• 一道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字节的空闲。

  • 相关阅读:
    linux常用命令三
    linux常用命令二
    redis持久化
    Linux环境redis集群搭建
    Maven常用命令
    Maven生成项目站点
    maven中import scope依赖方式解决单继承问题的理解
    maven使用assembly打包tar.gz文件
    Apache CXFjar包目录(转)
    WebService学习笔记一
  • 原文地址:https://www.cnblogs.com/abc123456789/p/3468040.html
Copyright © 2020-2023  润新知