C语言嵌入式系统编程修炼之内存操作.docx
《C语言嵌入式系统编程修炼之内存操作.docx》由会员分享,可在线阅读,更多相关《C语言嵌入式系统编程修炼之内存操作.docx(9页珍藏版)》请在冰点文库上搜索。
![C语言嵌入式系统编程修炼之内存操作.docx](https://file1.bingdoc.com/fileroot1/2023-7/8/dbaba2e6-e287-4785-a757-bcb4dac9710b/dbaba2e6-e287-4785-a757-bcb4dac9710b1.gif)
C语言嵌入式系统编程修炼之内存操作
C语言嵌入式系统编程修炼之内存操作
宋宝华
数据指针
在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的MOV指令,而除C/C++之外的其它编程语言大体没有直接访问绝对地址的能力。
在嵌入式系统的实际调试中,多借助C语言指针所具有的对绝对地址单元内容的读写能力。
以指针直接操作内存多发生在如下几种情形:
(1)某I/O芯片被定位在CPU的存储空间而非I/O空间,而且寄存器对应于某特定地址;
(2)两个CPU之间以双端口RAM通信,CPU需要在双端口RAM的特定单元(称为mailbox)书写内容以在对方CPU产生中断;
(3)读取在ROM或FLASH的特定单元所烧录的汉字和英文字模。
譬如:
unsignedchar*p=(unsignedchar*)0xF000FF00;
*p=11;
以上程序的意义为在绝对地址0xF0000+0xFF00(80186利用16位段地址和16位偏移地址)写入11。
在使用绝对地址指针时,要注意指针自增自减操作的结果取决于指针指向的数据类别。
上例中p++后的结果是p=0xF000FF01,若p指向int,即:
int*p=(int*)0xF000FF00;
p++(或++p)的结果等同于:
p=p+sizeof(int),而p-(或-p)的结果是p=p-sizeof(int)。
同理,若执行:
longint*p=(longint*)0xF000FF00;
那么p++(或++p)的结果等同于:
p=p+sizeof(longint),而p-(或-p)的结果是p=p-sizeof(longint)。
记住:
CPU以字节为单位编址,而C语言指针以指向的数据类型长度作自增和自减。
理解这一点对于以指针直接操作内存是相当重要的。
函数指针
第一要明白得以下三个问题:
(1)C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针;
(2)调用函数实际上等同于"调转指令+参数传递处理+回归位置入栈",本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器;
(3)因为函数调用的本质是跳转到某一个地址单元的code去执行,所以可以"调用"一个根本就不存在的函数实体,晕?
请往下看:
请拿出你能够取得的任何一本大学《微型运算机原理》教材,书中讲到,186CPU启动后跳转至绝对地址0xFFFF0(对应C语言指针是0xF000FFF0,0xF000为段地址,0xFFF0为段内偏移)执行,请看下面的代码:
typedefvoid(*lpFunction)();/*定义一个无参数、无返回类型的*/
/*函数指针类型*/
lpFunctionlpReset=(lpFunction)0xF000FFF0;/*定义一个函数指针,指向*/
/*CPU启动后所执行第一条指令的位置*/
lpReset();/*调用函数*/
在以上的程序中,咱们全然没有看到任何一个函数实体,可是咱们却执行了如此的函数挪用:
lpReset(),它事实上起到了"软重启"的作用,跳转到CPU启动后第一条要执行的指令的位置。
记住:
函数无它,唯指令集合耳;你可以调用一个没有函数体的函数,本质上只是换一个地址开始执行指令!
数组vs.动态申请
在嵌入式系统中动态内存申请存在比一样系统编程时更严格的要求,这是因为嵌入式系统的内存空间往往是十分有限的,不经意的内存泄露会专门快致使系统的崩溃。
所以一定要保证你的malloc和free成对出现,如果你写出这样的一段程序:
char*function(void)
{
char*p;
p=(char*)malloc(…);
if(p==NULL)
…;
…/*一系列针对p的操作*/
returnp;
}
在某处挪用function(),用完function中动态申请的内存后将其free,如下:
char*q=function();
…
free(q);
上述代码明显是不合理的,因为违背了malloc和free成对显现的原那么,即"谁申请,就由谁释放"原那么。
不知足那个原那么,会致使代码的耦合度增大,因为用户在挪用function函数时需要明白其内部细节!
正确的做法是在调用处申请内存,并传入function函数,如下:
char*p=malloc(…);
if(p==NULL)
…;
function(p);
…
free(p);
p=NULL;
而函数function那么接收参数p,如下:
voidfunction(char*p)
{
…/*一系列针对p的操作*/
}
大体上,动态申请内存方式能够用较大的数组替换。
关于编程新手,笔者推荐你尽可能采纳数组!
嵌入式系统能够以博大的胸怀接收瑕疵,而无法"海纳"错误。
毕竟,以最笨的方式苦练神功的郭靖胜过机智伶俐却范政治错误走反革命道路的杨康。
给出原则:
(1)尽可能的选用数组,数组不能越界访问(真理越过一步就是谬误,数组越过界限就光荣地成全了一个混乱的嵌入式系统);
(2)如果使用动态申请,则申请后一定要判断是否申请成功了,并且malloc和free应成对出现!
关键字const
const意味着"只读"。
区别如下代码的功能超级重要,也是老生长叹,若是你还不明白它们的区别,而且已经在程序界摸爬滚打连年,那只能说这是一个悲伤:
constinta;
intconsta;
constint*a;
int*consta;
intconst*aconst;
(1)关键字const的作用是为给读你代码的人转达超级有效的信息。
例如,在函数的形参前添加const关键字意味着那个参数在函数体内可不能被修改,属于"输入参数"。
在有多个形参的时候,函数的挪用者能够凭借参数前是不是有const关键字,清楚的分辨哪些是输入参数,哪些是可能的输出参数。
(2)合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改,这样可以减少bug的出现。
const在C++语言中则包含了更丰富的含义,而在C语言中仅意味着:
"只能读的普通变量",可以称其为"不能改变的变量"(这个说法似乎很拗口,但却最准确的表达了C语言中const的本质),在编译阶段需要的常数仍然只能以#define宏定义!
故在C语言中如下程序是非法的:
constintSIZE=10;
chara[SIZE];/*非法:
编译阶段不能用到变量*/
关键字volatile
C语言编译器会对用户书写的代码进行优化,譬如如下代码:
inta,b,c;
a=inWord(0x100);/*读取I/O空间0x100端口的内容存入a变量*/
b=a;
a=inWord(0x100);/*再次读取I/O空间0x100端口的内容存入a变量*/
c=a;
极可能被编译器优化为:
inta,b,c;
a=inWord(0x100);/*读取I/O空间0x100端口的内容存入a变量*/
b=a;
c=a;
可是如此的优化结果可能致使错误,若是I/O空间0x100端口的内容在执行第一次读操作后被其它程序写入新值,那么其实第2次读操作读出的内容与第一次不同,b和c的值应该不同。
在变量a的概念前加上volatile关键字能够避免编译器的类似优化,正确的做法是:
volatileinta;
volatile变量可能用于如下几种情形:
(1)并行设备的硬件寄存器(如:
状态寄存器,例中的代码属于此类);
(2)一个中断服务子程序中会访问到的非自动变量(也就是全局变量);
(3)多线程应用中被几个任务共享的变量。
CPU字长与存储器位宽不一致处置
在背景篇中提到,本文特意选择了一个与CPU字长不一致的存储芯片,确实是为了进行本节的讨论,解决CPU字长与存储器位宽不一致的情形。
80186的字长为16,而NVRAM的位宽为8,在这种情形下,咱们需要为NVRAM提供读写字节、字的接口,如下:
typedefunsignedcharBYTE;
typedefunsignedintWORD;
/*函数功能:
读NVRAM中字节
*参数:
wOffset,读取位置相对NVRAM基地址的偏移
*返回:
读取到的字节值
*/
externBYTEReadByteNVRAM(WORDwOffset)
{
LPBYTElpAddr=(BYTE*)(NVRAM+wOffset*2);/*为什么偏移要×2?
*/
return*lpAddr;
}
/*函数功能:
读NVRAM中字
*参数:
wOffset,读取位置相对NVRAM基地址的偏移
*返回:
读取到的字
*/
externWORDReadWordNVRAM(WORDwOffset)
{
WORDwTmp=0;
LPBYTElpAddr;
/*读取高位字节*/
lpAddr=(BYTE*)(NVRAM+wOffset*2);/*为什么偏移要×2?
*/
wTmp+=(*lpAddr)*256;
/*读取低位字节*/
lpAddr=(BYTE*)(NVRAM+(wOffset+1)*2);/*为什么偏移要×2?
*/
wTmp+=*lpAddr;
returnwTmp;
}
/*函数功能:
向NVRAM中写一个字节
*参数:
wOffset,写入位置相对NVRAM基地址的偏移
*byData,欲写入的字节
*/
externvoidWriteByteNVRAM(WORDwOffset,BYTEbyData)
{
…
}
/*函数功能:
向NVRAM中写一个字*/
*参数:
wOffset,写入位置相对NVRAM基地址的偏移
*wData,欲写入的字
*/
externvoidWriteWordNVRAM(WORDwOffset,WORDwData)
{
…
}
子贡问曰:
Why偏移要乘以2?
子曰:
请看图1,16位80186与8位NVRAM之间互连只能以地址线A1对其A0,CPU本身的A0与NVRAM不连接。
因此,NVRAM的地址只能是偶数地址,故每次以0x10为单位前进!
图1CPU与NVRAM地址线连接
子贡再问:
Sowhy80186的地址线A0不与NVRAM的A0连接?
子曰:
请看《IT论语》之《微机原理篇》,那里面讲述了关于计算机组成的圣人之道。
总结
本篇要紧讲述了嵌入式系统C编程中内存操作的相关技术。
把握并深切明白得关于数据指针、函数指针、动态申请内存、const及volatile关键字等的相关知识,是一个优秀的C语言程序设计师的大体要求。
当咱们已经牢固把握了上述技术后,咱们就已经学会了C语言的99%,因为C语言最精华的内涵皆在内存操作中表现。
我们之所以在嵌入式系统中使用C语言进行程序设计,99%是因为其强大的内存操作能力!
如果你爱编程,请你爱C语言;
如果你爱C语言,请你爱指针;
如果你爱指针,请你爱指针的指针!