嵌入式系统编程之内存操作的注意事项
内容提要
嵌入式系统编程中需要注意对特定内存单元的读写操作,使用指针操作内存时要注意数据类型,函数指针可用于跳转到特定地址执行指令,动态内存申请时要保证malloc和free成对出现,使用const关键字保护不希望被修改的参数,要注意数组越界访问和CPU字长与存储器位宽不一致的处理。
延伸解读
指针自增的陷阱:类型决定步长
文章强调,C语言指针的自增自减以指向的数据类型长度为单位,而CPU以字节编址。例如,unsigned char指针加1移动1字节,int指针加1移动sizeof(int)字节。在直接操作绝对地址时,若忽略这一点,可能读写到错误的内存单元,导致数据错乱或硬件异常。理解指针类型与地址步长的关系,是安全操作特定内存单元的前提。
函数指针:跳转到任意地址执行
函数名本质是代码在内存中的地址,函数调用即跳转到该地址执行。文章指出,可以定义函数指针指向一个绝对地址,即使该处没有对应的函数实体,也能通过调用指针来执行指令。例如,将函数指针指向CPU启动后的第一条指令地址,调用它可实现软重启。这揭示了函数调用的底层机制,但也要求开发者对目标地址的代码有准确把握。
动态内存:谁申请,谁释放
嵌入式系统内存有限,内存泄露会迅速导致崩溃。文章建议遵循“谁申请,谁释放”原则:在调用处申请内存并传入函数,函数只负责操作,不负责释放。这样可降低耦合度,避免调用者需要了解函数内部细节。对于编程新手,文章推荐尽量用数组替代动态申请,因为数组越界虽危险,但至少不会因忘记释放而造成持续泄露。
volatile与const:编译器的边界
volatile用于防止编译器优化,确保每次访问都从内存或I/O端口读取,适用于硬件寄存器、中断服务程序中的全局变量以及多任务共享变量。const则向编译器与读者传达“只读”意图,保护函数输入参数不被意外修改。但文章提醒,在C语言中const变量并非编译期常量,不能用于数组维度等需要常量表达式的场合,此时仍需使用#define。
Q&A
嵌入式编程中,用指针直接操作绝对地址时,指针自增自减的结果取决于什么?
取决于指针指向的数据类型。CPU以字节为单位编址,而C语言指针以指向的数据类型的长度进行自增和自减。例如,unsigned char指针p++后地址加1,int指针p++后地址加sizeof(int),long int指针p++后地址加sizeof(long int)。
函数指针如何实现跳转到特定地址执行指令?可以调用不存在的函数吗?
可以。函数名对应函数代码在内存中的地址,函数调用本质是跳转到该地址执行。因此可以将一个绝对地址强制转换为函数指针并调用,即使没有函数实体。例如,定义函数指针指向CPU启动后第一条指令的地址(如0xF000FFF0),调用它就能实现软重启。
嵌入式系统中动态内存申请为什么要求malloc和free成对出现?
因为嵌入式系统内存有限,不经意的内存泄露会很快导致系统崩溃。必须遵循“谁申请,就由谁释放”的原则,避免代码耦合度增大。推荐尽量使用数组替代动态申请,如果使用动态申请,申请后一定要判断是否成功,并确保malloc和free成对出现。
在C语言中,const关键字有什么作用?使用它有什么好处?
const意味着“只读”,用于保护不希望被修改的参数。在函数形参前加const,表示该参数在函数体内不会被修改,属于输入参数,便于调用者区分输入和输出参数。合理使用const可以让编译器保护参数,减少bug。在C语言中,const变量本质是“不能改变的变量”,不能用于编译阶段的常数(如数组大小),此时仍需用#define。
volatile关键字在嵌入式编程中有什么作用?哪些情况下需要使用?
volatile用于防止编译器优化,确保每次读取I/O空间或可能被外部修改的变量时都从内存中重新读取。适用于:并行设备的硬件寄存器(如状态寄存器)、中断服务子程序中访问的非自动变量(全局变量)、多线程应用中被几个任务共享的变量。
当CPU字长与存储器位宽不一致时,如何为NVRAM提供读写接口?
需要为NVRAM提供读写字节和字的接口。例如,16位CPU与8位NVRAM互连时,地址线A1连接NVRAM的A0,CPU的A0不连接,因此NVRAM地址只能是偶数地址,每次以0x10为单位前进。读写字时,需要分别读取高位字节和低位字节,并组合成字。