设为首页 加入收藏

TOP

C语言内存使用的常见问题及解决之道(四)
2015-01-22 21:26:28 来源: 作者: 【 】 浏览:158
Tags:语言 内存 使用 常见问题 解决
5 ? ? ?index = maxIndex; \
6 ? ? } \
7 }while(0)
复制代码
2.1.2 多重定义
? ? ?函数和定义时已初始化的全局变量是强符号;未初始化的全局变量是弱符号。多重定义的符号只允许最多一个强符号。Unix链接器使用以下规则来处理多重定义的符号:
?
? ? ?规则一:不允许有多个强符号。在被多个源文件包含的头文件内定义的全局变量会被定义多次(预处理阶段会将头文件内容展开在源文件中),若在定义时显式地赋值(初始化),则会违反此规则。
?
? ? ?规则二:若存在一个强符号和多个弱符号,则选择强符号。
?
? ? ?规则三:若存在多个弱符号,则从这些弱符号中任选一个。
?
? ? ?当不同文件内定义同名(即便类型和含义不同)的全局变量时,该变量共享同一块内存(地址相同)。若变量定义时均初始化,则会产生重定义(multiple definition)的链接错误;若某处变量定义时未初始化,则无链接错误,仅在因类型不同而大小不同时可能产生符号大小变化(size of symbol `XXX' changed)的编译警告。在最坏情况下,编译链接正常,但不同文件对同名全局变量读写时相互影响,引发非常诡异的问题。这种风险在使用无法接触 源码的第三方库时尤为突出。
?
? ? ?下面的例子编译链接时没有任何警告和错误,但结果并非所愿:
?
复制代码
?1 //test.c
?2 int gdwCount = 0;
?3 int GetCount(void)
?4 {
?5 ? ? return gdwCount;
?6 }
?7?
?8?
?9 //main.c
10 extern int GetCount(void);
11 int gdwCount;
12 int main(void)
13 {
14 ? ? gdwCount = 10;
15 ? ? printf("GetCount=%d\n", GetCount());
16 return 0;
17 }
复制代码
? ? ?编码者期望函数GetCount的返回值打印出来是0,但其实是10。若将main.c中的int gdwCount语句改为int gdwCount = 0,编译链接时就会报告multiple definition of 'gdwCount'的错误。因此尽量不要依赖和假设这种符号规则。
?
? ? ?关于全局符号多重定义的讨论,详见《C语言头文件组织与包含原则》一文。
?
? ? 【对策】尽量避免使用全局变量。若确有必要,应采用静态全局变量(无强弱之分,且不会和其他全局符号产生冲突),并封装访问函数供外部文件调用。
?
2.1.3 volatile修饰
? ? ?关键字volatile用于修饰易变的变量,告诉编译器该变量值可能会在任意时刻被意外地改变,因此不要试图对其进行任何优化。每次访问(读写)volatile所修饰的变量时,都必须从该变量的内存区域中重新读取,而不要使用寄存器(CPU)中保存的值。这样可保证数据的一致性,防止由于变量优化而出错。
?
? ? ?以下几种情况通常需要volatile关键字:
?
外围并行设备的硬件寄存器(如状态寄存器);
中断服务程序(ISR)中所访问的非自动变量(Non-automatic Variable),即全局变量;
多线程并发环境中被多个线程所共享的全局变量。
? ? ?变量可同时由const和volatile修饰(如只读的状态寄存器),表明它可能被意想不到地改变,但程序不应试图修改它。指针可由volatile修饰(尽管并不常见),如中断服务子程序修改一个指向某buffer的指针时。又如:
?
1 //只读端口(I/O与内存共享地址空间,非IA架构)
2 const volatile char *port = (const volatile char *)0x01F7
? ? ?误用volatile关键字可能带来意想不到的错误,例如:
?
1 int CalcSquare(volatile int *pVal)
2 {
3 ? ? return (*pVal) * (*pVal);
4 } //deficient
? ? ?函数CalcSquare返回指针pVal所指向值的平方,但由于该值被volatile修饰,编译器将产生类似下面的代码:
?
复制代码
1 int CalcSquare(volatile int *pVal)
2 {
3 ? ? int dwTemp1, dwTemp2;
4 ? ? dwTemp1 = *pVal;
5 ? ? dwTemp2 = *pVal;
6 ? ? return dwTemp1 * dwTemp2;
7 }//deficient
复制代码
? ? ?多线程环境下,指针pVal所指向值在函数CalcSquare执行时可能被意想不到地该变,因此dwTemp1和dwTemp2的取值可能不同,最终未必返回期望的平方值。
?
? ? ?正确的代码如下(使用全局变量的拷贝也是提高线程安全性的一种方法):
?
1 long CalcSquare(volatile int *pVal)
2 {
3 ? ? int dwTemp;
4 ? ? dwTemp = *pVal;
5 ? ? return dwTemp * dwTemp;
6 }//deficient
? ? ?再举一例:
?
1 #define READ(val, addr) ?(val = *(unsigned long *)addr)
? ? ?编译器优化这段代码时,若addr地址的数据读取太频繁,优化器会将该地址上的值存入寄存器中,后续对该地址的访问就转变为直接从寄存器中读取数据,如此将大大加快数据读取速度。但在并发操作时,一个进程读取数据,另一进程修改数据,这种优化就会造成数据不一致。此时,必须使用volatile修饰符。
?
? ? 【对策】合理使用volatile修饰符。
?
?
?
2.2 栈区内存
2.2.1 内存未初始化
? ? ?未初始化的栈区变量其内容为随机值。直接使用这些变量会导致不可预料的后果,且难以排查。
?
? ? ?指针未初始化(野指针)或未有效初始化(如空指针)时非常危险,尤以野指针为甚。
?
? ? 【对策】在定义变量时就对其进行初始化。某些编译器会对未初始化发出警告信息,便于定位和修改。
?
2.2.2 堆栈溢出
? ? ?每个线程堆栈空间有限,稍不注意就会引起堆栈溢出错误。注意,此处“堆栈”实指栈区。
?
1 #define MAX_SIZE ?3200000 ?// 系统不同该值不同(ulimit –s: 10240kbytes)
2 int main(void){
3 ? ? int aStackCrasher[MAX_SIZE] = {0}; ?//可能导致Segmentation fault
4 ? ? aStackCrasher[0] = 1;
5 ? ? return 0;
6 }
? ? ?堆栈溢出主要有两大原因:1) 过大的自动变量;2) 递归或嵌套调用层数过深。
?
? ? ?有时,函数自身并未定义
首页 上一页 1 2 3 4 5 6 7 下一页 尾页 4/7/7
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇C语言里为何会有“2+2=5”的结果 下一篇排序(6)---------归并排序(C语言..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: