数据指针
在嵌入式系统编程中,经常会对特定的内存单元进行读写操作。在汇编中有对应的MOV指令,而在C/C++以及其他高级语言中基本没有直接操作绝对地址的能力。而在嵌入式系统的实际开发调试过程中,大多情况会借助C语言指针多具备的对绝对地址单元内容的读写能力。
直接使用指针操作内存,主要分为以下几种情况:
-
某I/O芯片被定位在CPU的存储空间而非I/O空间,而且寄存器对应的是特定的地址
-
两个CPU之间以双端口RAM进行通信,CPU需要在双端口RAM的特定单元(mail box)书写内容以在对方CPU产生中断。
-
读写ROM或FLASH的特定单元所烧录的汉字或英文字模
////< 在绝对地址0x01写入10
unsigned char *p = (unsigned char *)0x01;
*p = 10;
使用绝对地址时,需要注意指针自增自减的结果取决于指针指向的数据类型。
int *p = (int *)0x01;
p++的结果为:p = p + sizeof(int)
p--的结果为:p = p - sizeof(int)
cpu以字节为单位编址,而C语言指针以指向的数据类型长度作为自增自减
函数指针
-
C 语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针
-
调用函数实际上等同于"调转指令+参数传递处理+回归位置入栈",本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU 的PC 寄存器
-
因为函数调用的本质是跳转到某一个地址单元的code 去执行,所以可以"调用"一个根本就不存在的函数实体
typedef void (*lpFunction) ( ); ////< 定义一个无参数、无返回类型的函数指针类型
////< 定义一个函数指针,指向CPU 启动后所执行第一条指令的位置
lpFunction lpReset = (lpFunction)0xF000FFF0;
lpReset(); ////< 函数调用
《微型计算机原理》中讲到,186 CPU 启动后跳转至绝对地址0xFFFF0,该地址对应到C语言中为上述例子中的0xF000FFF0,其中0xF000为段地址,0xFFF0为段内偏移。
在上述例子中,根本没有定义函数,但是却能够执行lpReset()函数的调用,它实际的作用为"软重启",使程序跳转到CPU启动后第一条要执行的执行位置。
动态申请内存
在嵌入式系统中动态内存申请存在比一般系统编程时更严格的要求,这是因为嵌入式系统的内存空间往往是十分有限的,不经意的内存泄露会很快导致系统的崩溃。
因此,maloc和free一般要保证成对出现。
char *Function(void) {
char *p = NULL;
p = (char *)malloc(sizeof(char));
...
return p;
}
void main() {
char *q = Function();
...
free(q);
}
上面这个例子明显是不合理的,因为它违反了"谁申请,就由谁释放"的原则。若程序不满足这个原则的话,会导致代码的耦合度增大,使用者需要在调用的时候知道Function()函数中的具体实现。
正确做法应该如下:
void Function(char*p) {
...
}
void main() {
char *q = malloc(sizeof(char));
...
Function(q);
...
free(q);
}