一道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
",*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
",&count,count); 
       printf("content at adress %p: 0x%X
",&k,k); 
       printf("content at adress %p: 0x%X
",&i,i);
       
       unsigned char * ptr = (unsigned char *)&i; 

       for(k = 0;k<4;k++) 
       { 
               printf("content at adress %p: 0x%X
",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
",&count,count); 
       printf("K at adress %p: 0x%X
",&k,k);
       printf("S at adress %p
",&s);
       printf("S.i at adress %p
",&s.i); 
       printf("S.j at adress %p
",&s.j); 
       printf("S.k at adress %p
",&s.k); 
       printf("I at adress %p: 0x%X
",&i,i);
       
 //      unsigned char * ptr = (unsigned char *)&i; 

//       for(k = 0;k<4;k++) 
 //      { 
  //             printf("content at adress %p: 0x%X
",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
",&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
" (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
",&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
" (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
",&s);
013B13FB  mov         esi,esp  
013B13FD  lea         eax,[ebp-2Ch]  
013B1400  push        eax  
013B1401  push        offset string "S at adress %p
" (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
",&s.i); 
013B1416  mov         esi,esp  
013B1418  lea         eax,[ebp-2Ch]  
013B141B  push        eax  
013B141C  push        offset string "S.i at adress %p
" (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
",&s.j); 
013B1431  mov         esi,esp  
013B1433  lea         eax,[ebp-28h]  
013B1436  push        eax  
013B1437  push        offset string "S.j at adress %p
" (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
",&s.k); 
013B144C  mov         esi,esp  
013B144E  lea         eax,[ebp-24h]  
013B1451  push        eax  
013B1452  push        offset string "S.k at adress %p
" (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
",&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
" (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
",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字节的空闲。

原文地址:https://www.cnblogs.com/abc123456789/p/3468040.html