完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980
本章教程为大家介绍数据类型,变量和堆栈的相关知识。
9.1 初学者重要提示
9.2 数据类型
9.3 局部变量和全局变量
9.4 堆栈
9.5 局部变量,全局变量和堆栈实例
9.6 总结
1、 如果对C语言不熟练的话,可以阅读C语言书:C Primer Plus(第五版)中文版.pdf
论坛下载:http://forum.armfly.com/forum.php?mod=viewthread&tid=91219。
2、 为了更好的学习本章知识点,可以看之前做的视频教程第10章,针对H7也将在今年发布视频教程:
http://forum.armfly.com/forum.php?mod=viewthread&tid=15408 。
了解数据类型之前要对ANSI C和ISO C的发展史有个了解,特别是C89,C99和C11的由来。
对于我们常用的编译器MDK和IAR而已,C89,C99和C11均支持。
ARM架构(含Cortex-M系列)的数据类型定义如下:
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
看程序的时候,经常会看到各种各样的变量命名方式,例如声明一个8位无符号整数,常见的有如下几种写法:
u8 err;
U8 err;
INT8U err;
UINT8 err;
uint8 err;
uint8_t err;
当大家阅读别人写的程序时,往往会看到风格各异的定义方式,移植部分程序时也不知道采用哪种方式更合适。
我们推荐大家采用最后一种定义方式,这种方法符合C99规范,ST的固件库都是采用的这种类型定义方式。像我们开发板配套的STM32例程,从2009年最初的版本开始就一直沿用C99的标准写法来定义整数。
针对变量声明问题,之前专门发过一个详细的帖子:
http://forum.armfly.com/forum.php?mod=viewthread&tid=501
在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的,这称为局部变量。
使用局部变量注意以下问题:
在函数内部定义的变量是局部变量,而在函数之外定义的变量称为外部变量,也就是全局变量。使用全局变量的注意事项:
程序设计中,建议不要创建太多的全局变量,主要是出于以下三点考虑:
从变量的作用域来分,可以分为全局变量和局部变量,而从变量值存在的时间来看,可以分为静态存储方式和动态存储方式。
全局变量存储在静态存储区中,动态存储区可以存放以下数据:
有时候希望函数中的局部变量的值在函数调用结束后不消失而保留原值,即占用的存储单元不释放,在下一次该函数调用时,该变量已有值,就是上一次函数调用结束时的值。这时可以使用关键字static进行声明。
用static声明一个变量的作用:
栈(stack)空间,用于局部变量,函数调时现场保护和返回地址,函数的形参等。
堆(heap)空间,主要用于动态内存分配,也就是说用 malloc,calloc, realloc等函数分配的变量空间是在堆上。以STM32H7为例,堆栈是在startup_stm32h743xx.s文件里面设置:
Cortex – M7/M4/M3 处理器拥有R0-R15的通用寄存器组。其中R13作为堆栈指针SP。SP有两个,但在同一时刻只能有一个可以用。
另外以下两点要注意:
这个知识点在以后用H7移植RTOS时,非常有用。
这里对入栈和出栈做个简单的介绍。PUSH入栈操作:SP先自减 4,再存入新的数值:
POP出栈操作:先从SP指针处读出上一次被压入的值,再把SP指针自增 4:
通过下面的实例可以对局部变量,全局变量和堆栈有个感性的认识:
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函数里面查看:
C语言的基础知识点要掌握牢靠,对于后面学习HAL库源码大有裨益。