一道C语言的问题(转)
在网上看见一个人的博客(http://blog.csdn.net/liumangxiong/article/details/670884),说的是一个C语言的问题
看起来有点兴趣,对于第一个问题好疑惑,虽然p指向了局部变量,但怎么会死循环呢??
只好来用他所说的第三种方法(。。。)来测试下
首先上个代码吧
#include "stdio.h" int *p = NULL; int *fFun(void) { int i = 0; return &i; } void subFun(void) { (*p)--; } void gFun(void) { int j; for(j = 0;j<10;j++) { subFun(); printf("%d/n",j); } } int _tmain(int argc, _TCHAR* argv[]) { p = fFun(); gFun(); return 0; }
简单运行下,即可发现,为什么会死循环呢,因为在subFun()执行后j都执行了--操作。。。
看来p指向的内存就是j....
如果在int j前再加个int k,则p应该指向的就是k了
即如果将gFun修改如下,则打印结果应该是*p=1111
void gFun(void) { int k=1111; int j; printf("*p=%d\n",*p); for(j = 0;j<10;j++) { subFun(); printf("%d/n",j); } }
下面再来第二段代码
#include <stdio.h> int _tmain(int argc, _TCHAR* argv[]) { int count = 4; int k=0; int i = 0xFEDCBA98; printf("content at adress %p: 0x%X\n",&count,count); printf("content at adress %p: 0x%X\n",&k,k); printf("content at adress %p: 0x%X\n",&i,i); unsigned char * ptr = (unsigned char *)&i; for(k = 0;k<4;k++) { printf("content at adress %p: 0x%X\n",ptr,*ptr); ptr++; } return 0; }
运行结果如下
从结果可以看出,可以得出几个结论:
1. 栈的生长方向是向内存小地址伸展的
2. 在windows下,多字节类型的数据,比如int,是little-endian
(BIG-ENDIAN就是低位字节排放在内存的高端,高位字节排放在内存的低端。而LITTLE-ENDIAN正好相反。)
3. 对于多字节类型的数据,它的指针是指向小内存地址的,即*(unsigned char*)&i=0x98,而不是*(unsigned char*)&i=0xFE
4. 为什么count,k,i等局部变量之间相差12字节呢?(0x64-0x58=0x58-0x4c=12)
对上面第二段代码稍作扩展,有下面代码
#include <stdio.h> struct S{ int i,j,k; }; int _tmain(int argc, _TCHAR* argv[]) { int count = 4; int k=0; S s; int i = 0xFEDCBA98; printf("COUNT at adress %p: 0x%X\n",&count,count); printf("K at adress %p: 0x%X\n",&k,k); printf("S at adress %p\n",&s); printf("S.i at adress %p\n",&s.i); printf("S.j at adress %p\n",&s.j); printf("S.k at adress %p\n",&s.k); printf("I at adress %p: 0x%X\n",&i,i); // unsigned char * ptr = (unsigned char *)&i; // for(k = 0;k<4;k++) // { // printf("content at adress %p: 0x%X\n",ptr,*ptr); // ptr++; // } return 0; }
在windows的vs2010运行结果如下
从这里发现结构体的指针依然也是指向低内存地址,但为什么s.k在内存高位,而不是内存低位呢??
对上面第三段代码进行反汇编,结果如下
int _tmain(int argc, _TCHAR* argv[]) { 013B1380 push ebp //保存main函数初始地址 013B1381 mov ebp,esp //EBP设为当前堆栈指针(esp) 013B1383 sub esp,0FCh //预留252字节(0x0FCh)给函数临时变量 013B1389 push ebx //EBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址 013B138A push esi //ESI/EDI 分别叫做"源/目标索引寄存器"(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串. 013B138B push edi 013B138C lea edi,[ebp-0FCh] //lea会把地址,而不是地址里的内容送入寄存器 013B1392 mov ecx,3Fh //ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器.3Fh=63 013B1397 mov eax,0CCCCCCCCh //EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器.0CCCCCCCCh是int3中断 013B139C rep stos dword ptr es:[edi] //stos((store into String),意思是把eax的内容拷贝到目的地址(es:[edi]指向目标串,ds:[esi]指向源串) //dword ptr前缀告诉stos,一次拷贝双字(4个字节)的数据到目的地址 //REP可以是任何字符传指令(CMPS, LODS, MOVS, SCAS, STOS)的前缀. REP能够引发其后的字符串指令被重复, 只要ecx的值不为0, 重复就会继续. //每一次字符串指令执行后, ecx的值都会减小.因为这里会重复63次(3Fh),63*4=252,刚好初始化上面预留的252字节的空间 013B139E mov eax,dword ptr [___security_cookie (13B7000h)] 013B13A3 xor eax,ebp 013B13A5 mov dword ptr [ebp-4],eax //这几句的意思是,在取到___security_cookie之后,会和当前的ebp 相异或(xor),异或的值保持在当前stack 的顶部,作函数结束标记 int count = 4; 013B13A8 mov dword ptr [ebp-0Ch],4 //0Ch = 12,ebp为函数的初始地址,所以count存在函数的高内存地址处,这里12可能是考虑最大基本类型的长度 int k=0; 013B13AF mov dword ptr [ebp-18h],0 //18h = 24 S s; //为什么这个结构体没有汇编码,而是直接赋给其12字节(两个变量之间相差8字节)??推测结构体大小计算(sizeof)应该是在编译期,而不是运行期 int i = 0xFEDCBA98; 013B13B6 mov dword ptr [ebp-38h],0FEDCBA98h //38h = 56 = (36+20) printf("COUNT at adress %p: 0x%X\n",&count,count); 013B13BD mov esi,esp //进入printf函数前,esi保存当前堆栈指针 013B13BF mov eax,dword ptr [ebp-0Ch] //EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器.0Ch = 12 013B13C2 push eax //printf参数3压栈,这里可以发现参数是从右向左入栈 013B13C3 lea ecx,[ebp-0Ch] 013B13C6 push ecx //printf参数2压栈 013B13C7 push offset string "COUNT at adress %p: 0x%X\n" (13B57D0h) //printf参数1压栈 013B13CC call dword ptr [__imp__printf (13B82B0h)] //调用printf函数 013B13D2 add esp,0Ch //因为printf函数有3个参数,堆栈指针加3*4个值,恢复堆栈平衡 013B13D5 cmp esi,esp //比较esp和调用printf之前的堆栈地址是否一致(由esi保存) 013B13D7 call @ILT+305(__RTC_CheckEsp) (13B1136h) //debug函数,检查堆栈平衡 printf("K at adress %p: 0x%X\n",&k,k); 013B13DC mov esi,esp 013B13DE mov eax,dword ptr [ebp-18h] 013B13E1 push eax 013B13E2 lea ecx,[ebp-18h] 013B13E5 push ecx 013B13E6 push offset string "K at adress %p: 0x%X\n" (13B57B4h) 013B13EB call dword ptr [__imp__printf (13B82B0h)] 013B13F1 add esp,0Ch 013B13F4 cmp esi,esp 013B13F6 call @ILT+305(__RTC_CheckEsp) (13B1136h) printf("S at adress %p\n",&s); 013B13FB mov esi,esp 013B13FD lea eax,[ebp-2Ch] 013B1400 push eax 013B1401 push offset string "S at adress %p\n" (13B57A0h) 013B1406 call dword ptr [__imp__printf (13B82B0h)] 013B140C add esp,8 013B140F cmp esi,esp 013B1411 call @ILT+305(__RTC_CheckEsp) (13B1136h) printf("S.i at adress %p\n",&s.i); 013B1416 mov esi,esp 013B1418 lea eax,[ebp-2Ch] 013B141B push eax 013B141C push offset string "S.i at adress %p\n" (13B5788h) 013B1421 call dword ptr [__imp__printf (13B82B0h)] 013B1427 add esp,8 013B142A cmp esi,esp 013B142C call @ILT+305(__RTC_CheckEsp) (13B1136h) printf("S.j at adress %p\n",&s.j); 013B1431 mov esi,esp 013B1433 lea eax,[ebp-28h] 013B1436 push eax 013B1437 push offset string "S.j at adress %p\n" (13B5770h) 013B143C call dword ptr [__imp__printf (13B82B0h)] 013B1442 add esp,8 013B1445 cmp esi,esp 013B1447 call @ILT+305(__RTC_CheckEsp) (13B1136h) printf("S.k at adress %p\n",&s.k); 013B144C mov esi,esp 013B144E lea eax,[ebp-24h] 013B1451 push eax 013B1452 push offset string "S.k at adress %p\n" (13B5758h) 013B1457 call dword ptr [__imp__printf (13B82B0h)] 013B145D add esp,8 013B1460 cmp esi,esp 013B1462 call @ILT+305(__RTC_CheckEsp) (13B1136h) printf("I at adress %p: 0x%X\n",&i,i); 013B1467 mov esi,esp 013B1469 mov eax,dword ptr [ebp-38h] 013B146C push eax 013B146D lea ecx,[ebp-38h] 013B1470 push ecx 013B1471 push offset string "I at adress %p: 0x%X\n" (13B573Ch) 013B1476 call dword ptr [__imp__printf (13B82B0h)] 013B147C add esp,0Ch 013B147F cmp esi,esp 013B1481 call @ILT+305(__RTC_CheckEsp) (13B1136h) // unsigned char * ptr = (unsigned char *)&i; // // for(k = 0;k<4;k++) // { // printf("content at adress %p: 0x%X\n",ptr,*ptr); // ptr++; // } return 0; 013B1486 xor eax,eax }
综上分析,有下面结论:
结构体的size计算应该发生在编译期,sizeof(struct s)=12,但结构体内部成员却是按大地址方向分配,即成员1放在最低位,成员2,成员3依次放在更高位,这样的分配是由于在结构体内,内存的分配主要是通过结构体基址+偏移量的方式,所以成员1是&s+0,成员2地址是&s+4...
同时还有以下几个问题待解决:
1. 为什么count,k,i,struct s等局部变量之间相差12字节?准确的说是8字节,虽然&count - &k =12,除掉count占用的4字节,所以就是8字节,同样struct s分配了20字节,s自身占用12字节,依然有8字节的空闲。