完整教程下载地址: http://forum.armfly.com/forum.php?mod=viewthread&tid=86980
第 9 章 STM32H7 重要知识点数据类型, 变量和堆栈
本章教程为大家介绍数据类型, 变量和堆栈的相关知识.
9.1 初学者重要提示
9.2 数据类型
9.3 局部变量和全局变量
9.4 堆栈
9.5 局部变量, 全局变量和堆栈实例
9.6 总结
9.1 初学者重要提示
1, 如果对 C 语言不熟练的话, 可以阅读 C 语言书: C Primer Plus(第五版)中文版. PDF
论坛下载:.
2, 为了更好的学习本章知识点, 可以看之前做的视频教程第 10 章, 针对 H7 也将在今年发布视频教程:
.
9.2 数据类型
了解数据类型之前要对 ANSI C 和 ISO C 的发展史有个了解, 特别是 C89,C99 和 C11 的由来.
9.2.1 ANSI C 和 ISO C 历史
1983 年, 美国国家标准协会 (ANSI) 组成了一个委员会来创立 C 语言的标准. 因为这个标准是 1989 年发布的, 所以一般简称 C89 标准. 有些人也把 C89 标准叫做 ANSI C.
在 1990 年, ANSI C89 标准被国际标准化组织 (ISO) 和国际电工委员会 (IEC) 采纳为国际标准, 名叫 ISO/IEC 9899:1990 - Programming languages C, 有些人简称 C90 标准. 因此, C89 和 C90 通常指同一个标准, 一般更常用 C89 这种说法.
在 2000 年 3 月, 国际标准化组织 (ISO) 和国际电工委员会 (IEC) 采纳了第二个 C 语言标准, 名叫 ISO/IEC 9899:1999 - Programming languages -- C, 简称 C99 标准.
在 2011 年 12 月, 国际标准化组织 (ISO) 和国际电工委员会 (IEC) 采纳了第三个 C 语言标准, 名叫 ISO/IEC 9899:2011 - Information technology -- Programming languages -- C, 简称 C11 标准. 它是 C 程序语言的最新标准.
对于我们常用的编译器 MDK 和 IAR 而已, C89,C99 和 C11 均支持.
9.2.2 ARM 架构 (含 Cortex-M 系列) 数据类型
ARM 架构 (含 Cortex-M 系列) 的数据类型定义如下:
9.2.3 头文件 stdint.h 对数据类型的定义
stdint.h 是 C99 中引进的一个标准 C 库的头文件. 目前大部分单片机 C 编译器均支持, IAR 和 MDK 都支持. 下面是部分内容(来自 MDK).
- /* exact-width signed integer types */
- typedef signed char int8_t;
- typedef signed short int int16_t;
- typedef signed int int32_t;
- typedef signed __INT64 int64_t;
- /* exact-width unsigned integer types */
- typedef unsigned char uint8_t;
- typedef unsigned short int uint16_t;
- typedef unsigned int uint32_t;
- typedef unsigned __INT64 uint64_t;
- /* 7.18.1.2 */
- /* smallest type of at least n bits */
- /* minimum-width signed integer types */
- typedef signed char int_least8_t;
- typedef signed short int int_least16_t;
- typedef signed int int_least32_t;
- typedef signed __INT64 int_least64_t;
- /* minimum-width unsigned integer types */
- typedef unsigned char uint_least8_t;
- typedef unsigned short int uint_least16_t;
- typedef unsigned int uint_least32_t;
- typedef unsigned __INT64 uint_least64_t;
- /* 7.18.1.3 */
- /* fastest minimum-width signed integer types */
- typedef signed int int_fast8_t;
- typedef signed int int_fast16_t;
- typedef signed int int_fast32_t;
- typedef signed __INT64 int_fast64_t;
- /* fastest minimum-width unsigned integer types */
- typedef unsigned int uint_fast8_t;
- typedef unsigned int uint_fast16_t;
- typedef unsigned int uint_fast32_t;
- typedef unsigned __INT64 uint_fast64_t;
以 MDK5.26 为例, stdint.h 位于如下路径:
\Keil_v526\ARM\ARMCC\include
以 IAR8.X 为, stdint.h 位于如下路径:
\IAR Systems\Embedded Workbench 8.1\ARM\inc\c
9.2.4 程序中推荐的变量命名方式
看程序的时候, 经常会看到各种各样的变量命名方式, 例如声明一个 8 位无符号整数, 常见的有如下几种写法:
- u8 err;
- U8 err;
- INT8U err;
- UINT8 err;
- uint8 err;
- uint8_t err;
当大家阅读别人写的程序时, 往往会看到风格各异的定义方式, 移植部分程序时也不知道采用哪种方式更合适.
我们推荐大家采用最后一种定义方式, 这种方法符合 C99 规范, ST 的固件库都是采用的这种类型定义方式. 像我们开发板配套的 STM32 例程, 从 2009 年最初的版本开始就一直沿用 C99 的标准写法来定义整数.
知识点拓展
针对变量声明问题, 之前专门发过一个详细的帖子:
9.3 局部变量和全局变量
9.3.1 局部变量
在一个函数内部定义的变量是内部变量, 它只在本函数范围内有效, 也就是说只有在本函数内才能使用它们, 在此函数以外是不能使用这些变量的, 这称为局部变量.
使用局部变量注意以下问题:
不同函数中可以使用相同名字的变量, 它们代表不同的对象, 互不干扰.
形式参数也是局部变量.
局部变量的作用域在函数内部.
9.3.2 全局变量
在函数内部定义的变量是局部变量, 而在函数之外定义的变量称为外部变量, 也就是全局变量. 使用全局变量的注意事项:
全局变量可以为本文件中其他函数所共用. 它的有效范围为从定义变量的位置开始到本源文件结束.
设置全局变量的作用是增加了函数间数据联系的渠道.
如果在同一个源文件中, 外部变量和局部变量同名, 则在局部变量的作用范围内, 外部变量被 "屏蔽", 即外部变量将不起作用.
9.3.3 使用全局变量的缺点
程序设计中, 建议不要创建太多的全局变量, 主要是出于以下三点考虑:
全局变量在程序的执行过程中都占用存储单元, 而不是仅在需要时才占用存储单元.
函数的通用性降低了, 因为函数在执行时要依赖于其所在的外部变量. 如果将一个函数移植到另一个文件中, 还要将有关的外部变量及其值一起移植过去.
使用全局变量过多, 会降低程序的清晰性, 特别是多个函数都调用此变量时.
9.3.4 变量的存储类别
从变量的作用域来分, 可以分为全局变量和局部变量, 而从变量值存在的时间来看, 可以分为静态存储方式和动态存储方式.
静态存储方式: 指在程序运行期间由系统分配固定的存储空间方式.
动态存储方式: 在程序运行期间根据需要进行动态的分配存储空间方式.
全局变量存储在静态存储区中, 动态存储区可以存放以下数据:
函数形式参数, 在调用函数时给形参分配存储空间.
局部变量(未加 static 声明的局部变量).
函数调用时的现场保护和返回地址等.
9.3.5 用 static 声明局部或者全局变量
有时候希望函数中的局部变量的值在函数调用结束后不消失而保留原值, 即占用的存储单元不释放, 在下一次该函数调用时, 该变量已有值, 就是上一次函数调用结束时的值. 这时可以使用关键字 static 进行声明.
用 static 声明一个变量的作用:
对局部变量用 static 声明, 则使用该变量在整个程序执行期间不释放, 为其分配的的空间始终存在.
全局变量用 static 声明, 则该变量的作用域只限于本文件模块(即被声明的文件中).
9.4 堆栈
9.4.1 堆栈作用
栈 (stack) 空间, 用于局部变量, 函数调时现场保护和返回地址, 函数的形参等.
堆 (heap) 空间, 主要用于动态内存分配, 也就是说用 malloc,calloc, realloc 等函数分配的变量空间是在堆上. 以 STM32H7 为例, 堆栈是在 startup_stm32h743xx.s 文件里面设置:
9.4.2 寄存器组(堆栈指针寄存器)
Cortex - M7/M4/M3 处理器拥有 R0-R15 的通用寄存器组. 其中 R13 作为堆栈指针 SP.SP 有两个, 但在同一时刻只能有一个可以用.
主堆栈指针(MSP): 这是缺省的堆栈指针, 它由 OS 内核, 异常服务例程以及所有需要特权访问的应用程序代码来使用.
进程堆栈指针(PSP): 用于常规的应用程序代码(不处于异常服务例程中时).
另外以下两点要注意:
大多数情况下的应用, 只需使用指针 MSP, 而 PSP 多用于 RTOS 中.
R13 的最低两位被硬线连接到 0, 并且总是读出 0, 这意味着堆栈总是 4 字节对齐的.
9.4.3 Cortex-M7/M4/M3 向下生长的满栈
这个知识点在以后用 H7 移植 RTOS 时, 非常有用.
9.4.4 堆栈的基本操作
这里对入栈和出栈做个简单的介绍. PUSH 入栈操作: SP 先自减 4, 再存入新的数值:
POP 出栈操作: 先从 SP 指针处读出上一次被压入的值, 再把 SP 指针自增 4:
9.5 局部变量, 全局变量和堆栈实例
通过下面的实例可以对局部变量, 全局变量和堆栈有个感性的认识:
- uint32_t a = 0; // 全局初始化区, 可以被其他 c 文件 extern 引用
- static uint32_t ss = 0; // 静态变量, 只允许在本文件使用
- uint8_t *p1; // 全局未初始化区
- int main(void)
- {
- uint32_t b; // 栈
- uint8_t s[] = "abc"; // 栈
- uint8_t *p2; // 栈
- uint8_t *p3 = "123456"; //123456\0 在常量区, p3 在栈上.
- static uint32_t c =0; // 全局 (静态) 初始化区
- p1 = (uint8_t *)malloc(10); // 在堆区申请了 10 个字节空间
- p2 = (uint8_t *)malloc(20); // 在堆区申请了 20 个字节空间
- strcpy(p1, "123456"); /* 123456 字符串 (结束符号是 0(\0), 总长度 7) 放在常量区,
- 编译器可能会将它与 p3 所指向的 "123456" 优化成一个地方 */
- }
通过查看 MAP 文件, 可以看全局变量在 RAM 中的位置:
- Symbol Name Value Ov Type Size Object(Section)
- a 0x20000000 Data 4 main.o(.data)
- p1 0x2000000c Data 4 main.o(.data)
- ss 0x20000004 Data 4 main.o(.data)
而局部变量要调整状态进入 main 函数里面查看:
9.6 总结
C 语言的基础知识点要掌握牢靠, 对于后面学习 HAL 库源码大有裨益.
来源: https://www.cnblogs.com/armfly/p/10751080.html