首页 > 代码库 > 阶段性研究

阶段性研究

变量存储区域问题

先上结论:

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

实现完全不同,这再次说明数组是一种独立的数据类型。

阶段性研究