变量存储区域问题
先上结论:
①static无论是全局变量还是局部变量都存储在全局/静态区域,在编译期就为其分配内存,在程序结束时释放,例如:val_a、val_d、val_h、val_i。
②const全局变量存储在只读数据段,编译期最初将其保存在符号表中,第一次使用时为其分配内存,在程序结束时释放,例如:val_c;const局部变量存储在栈中,代码块结束时释放,例如:val_j。
③全局变量存储在全局/静态区域,在编译期为其分配内存,在程序结束时释放,例如:val_b、val_e。
④局部变量存储在栈中,代码块结束时释放,例如:val_h、val_i。
注:当全局变量和静态局部变量未赋初值时,系统自动置为0。
为了验证各类数据所在的数据段,实现了一个简易的内存显示单元mem.h
//mem.h #ifndef __MEM_H #define __MEM_H #include <stdio.h> void display(void *srcptr); #include "mem.c" #endif
//mem.c void display(void *srcptr){ char *p, *pos; for (pos = (char *)((unsigned int)srcptr & 0xFFFFFFF0); pos < srcptr + 0x80; pos += 0x10){ printf("%06X: ", pos); for (p = pos; p < pos + 16; ++p){ if (p < srcptr || p >= srcptr + 0x80) printf(" "); else if (p == pos + 7) printf("%02X-", *p & 0xFF); else printf("%02X ", *p & 0xFF); } printf(" "); for (p = pos; p < pos + 16; ++p){ if (p < srcptr || p >= srcptr + 0x80) putchar(‘ ‘); else if (*p < 0x80 && *p >= 0x20) putchar(*p & 0xFF); else putchar(‘.‘); } putchar(‘\n‘); } putchar(‘\n‘); }
测试例程和结果如图:
全局变量,const全局变量,局部变量,const局部变量的存储:
常指针表示的字符串常量:
数组表示的字符串常量:
现在我们发现字符串常量用数组和常指针表示的内存分配情况有很大差异。
函数调用栈中无维数数组形参的传递:
const变量是否在第一次引用时分配内存
一直认为const变量是与同作用域的变量共同存在的,写程序验证如下:
虽未引用b和d,但显然为其分配了内存,仿佛与上面说法相悖,不妨开-O优化试试:
结果可喜可贺,未引用的const变量没有分配内存。以上程序经gcc/g++分别测试,结果相同。
const变量是否真正不可修改
尝试用指针修改const全局变量和const局部变量,做如下两组试验:
尝试修改const局部变量:
解释:const局部变量在栈中,无法保证其值不被修改,但为什么第二次输出a是5而不是2呢?
作如下试验程序:
int A, B; int main(){ const int a = 5; int * p = (int *)&a; *p = 2; A = *p; B = a; }
用GDB查看汇编代码如下:
//a = 5; 0x004013c8 <+24>: mov DWORD PTR [esp+0x8],0x5 //p = (int *)&a; 0x004013d0 <+32>: lea eax,[esp+0x8] 0x004013d4 <+36>: mov DWORD PTR [esp+0xc],eax //*p = 2; => 0x004013d8 <+40>: mov eax,DWORD PTR [esp+0xc] 0x004013dc <+44>: mov DWORD PTR [eax],0x2 //A = *p; 0x004013e2 <+50>: mov eax,DWORD PTR [esp+0xc] 0x004013e6 <+54>: mov eax,DWORD PTR [eax] 0x004013e8 <+56>: mov ds:0x406024,eax //B = a; 0x004013ed <+61>: mov DWORD PTR ds:0x406028,0x5
于是我们发现,编译器将a作为立即数5写入汇编代码,从而导致引用a值仍为5。
可见const可以保证某个变量是编译时常量。
尝试修改const全局变量:
错误很显然:const全局变量在只读区段,不允许修改。
数组与常指针的差异
一个显而易见的差异是元素大小不同:
sizeof (int [10]) = 10 * sizeof (int); sizeof (int * const) = sizeof (int *);
它影响到以之为基类的指针寻址:
int (*) [10]; int *const *;
尝试取数组名的地址,得到如下结果:
发现数组名的地址与数组名的值相同,而第一个单元存放的是数组的第一个元素,所以数组名绝不可能是一个指针。
C/C++把数组当作一种独立的数据类型处理。
为什么可以用数组名初始化指针呢?因为初始化发生了从int [N] -> int *的隐式转换。这种隐式转换并不影响用int *p对数组寻址。
用常指针为数组元素寻址与直接用数组寻址的差别:
//global int array[10] = {0}; int *const parray = array; //---------------------------------------------------------- //parray[0] = 1; // 指针parray的地址(偏移地址)是编译时常量0x404080 => 0x0040150b <+21>: mov eax,ds:0x404080 0x00401510 <+26>: mov DWORD PTR [eax],0x1 //array[0] = 1; // 数组array的地址(偏移地址)是编译时常量0x406040 0x00401516 <+32>: mov DWORD PTR ds:0x406040,0x1 //parray[1] = 1; => 0x0040150b <+21>: mov eax,ds:0x404080 0x00401510 <+26>: add eax,0x4 0x00401513 <+29>: mov DWORD PTR [eax],0x1 //array[1] = 1; 0x00401519 <+35>: mov DWORD PTR ds:0x406044,0x1 //int i = 1; 0x0040150e <+24>: mov DWORD PTR [esp+0xc],0x1 //parray[i] = 1; => 0x00401516 <+32>: mov eax,ds:0x404080 0x0040151b <+37>: mov edx,DWORD PTR [esp+0xc] 0x0040151f <+41>: shl edx,0x2 0x00401522 <+44>: add eax,edx 0x00401524 <+46>: mov DWORD PTR [eax],0x1 //array[i] = 1; 0x0040152a <+52>: mov eax,DWORD PTR [esp+0xc] 0x0040152e <+56>: mov DWORD PTR [eax*4+0x406040],0x1
实现完全不同,这再次说明数组是一种独立的数据类型。