C/C++函数内的数据存放方式

一般来说,我们写的代码都是在main函数内运行,main函数就是一切的核心,这的确没错,

main函数包含我们所写的代码的主要流程,我们会把想法灌注到其中去,写出一段段代码,最终编译出程序,

即使面向对象的应用开发也是如此。

 

不过main函数是个函数,它跟其他函数有没有什么根本上的区别?

答案是否定的,我们用main()作为函数的开头只是因为编译器这样子要求,

如果编译器要求我们用其他函数作为我们代码体的入口,那么main函数就只是一个单纯的函数。

 

以上说的只是为了表明函数中数据存放方式的通用性,如果需要了解更多上面描述的相关信息,可以去看看程序反汇编或者相关资料。

 

要用函数必须要设置栈(设置sp、ep,当然这是对于裸板程序来说的),栈是函数数据的基本表现形式,

当要调用某个函数的时候,我们需要保存所调用函数所用到的寄存器的数据,数据保存在栈内;

当有参数通过形参传给函数体时,通过栈来保存形参(某些编译器直接通过寄存器传数据,如果寄存器不够用的时候再用栈保存数据);

当我们在函数提内定义变量时,在栈内保存该变量(这只使用与一般情形,特殊情况下面会讲到);

寄存器sp是栈顶,ep是栈底,读写栈内数据就是通过sp跟ep来确定栈内地址,从而达到数据读写的目的

 

下面基于i386跟g++分析函数内数据存放方式

首先我们在main函数内定义了一段数据:

    int index=0;
    unsigned long sum=0;
    
    const int a=2;
    
    static int b=3;
    
    char * str[]={
        "hello ",
        "what ",
        "is ",
        "your ",
        "name ",
    };
    
    const unsigned long longnum[]={
        0x02345678,
        0x02345678,
        0x13245678,
        0x02345678,
        0x01345678,
        0x01245678,
        0x12345678,
        0x12345678,
    };

 

然后用objdump查看反汇编:

  4011ba:    c7 45 f4 00 00 00 00     movl   $0x0,-0xc(%ebp)                        //int index=0;
  4011c1:    c7 45 f0 00 00 00 00     movl   $0x0,-0x10(%ebp)                        //unsigned long sum=0;
  4011c8:    c7 45 ec 02 00 00 00     movl   $0x2,-0x14(%ebp)                        //const int a=2;
  
  4011cf:    c7 45 c8 80 21 44 00     movl   $0x442180,-0x38(%ebp)            //"hello "字符串首地址
  4011d6:    c7 45 cc 87 21 44 00     movl   $0x442187,-0x34(%ebp)            //"what "字符串首地址
  4011dd:    c7 45 d0 8d 21 44 00     movl   $0x44218d,-0x30(%ebp)            //"is "字符串首地址
  4011e4:    c7 45 d4 91 21 44 00     movl   $0x442191,-0x2c(%ebp)            //"your "字符串首地址
  4011eb:    c7 45 d8 97 21 44 00     movl   $0x442197,-0x28(%ebp)            //"name "字符串首地址
  
  4011f2:    c7 45 a8 78 56 34 02     movl   $0x2345678,-0x58(%ebp)            //0x02345678,
  4011f9:    c7 45 ac 78 56 34 02     movl   $0x2345678,-0x54(%ebp)            //0x02345678,
  401200:    c7 45 b0 78 56 24 13     movl   $0x13245678,-0x50(%ebp)        //0x13245678,
  401207:    c7 45 b4 78 56 34 02     movl   $0x2345678,-0x4c(%ebp)            //0x02345678,
  40120e:    c7 45 b8 78 56 34 01     movl   $0x1345678,-0x48(%ebp)            //0x01345678,
  401215:    c7 45 bc 78 56 24 01     movl   $0x1245678,-0x44(%ebp)            //0x01245678,
  40121c:    c7 45 c0 78 56 34 12     movl   $0x12345678,-0x40(%ebp)        //0x12345678,
  401223:    c7 45 c4 78 56 34 12     movl   $0x12345678,-0x3c(%ebp)        //0x12345678,

 

00442180 <.rdata>:
  442180:    68 65 6c 6c 6f     //"hello "
  442185:    20 00            
  442187:    77 68            //"what "
  442189:    61                
  44218a:    74 20             
  44218c:    00 69 73         //"is "
  44218f:    20 00          
  442191:    79 6f            // "your "
  442193:    75 72            
  442195:    20 00             
  442197:    6e               //"name "
  442198:    61               
  442199:    6d               
  44219a:    65 20 00            
  44219d:    00 00     

 

由上面可以看出,int index =0;  unsigned long sum = 0;  const int a = 2;  都是在栈内自动生成(这些就是前面所说的一般情况)

而 const * str[]={...}倒是从.rodata  (只读数据段)取数据

const unsigned longnum[]={...}  里面的数据全部都在栈内自动生成。

 

因此我们可以得出这样一个结论:

在该编译器,栈内保存的都是类型化的(int,char,long,unsigned ..)的数据,并且对于重复性的类型化数据(如数组),也是保存在栈内。但是对于复杂性的数据(如:字符串),在只读数据段中保存。

 

 

然后如果我们仔细观察上面的代码会发现,static int b = 3;没出现在上示代码段中,我们可以通过要求输出b来得到该变量的位置。

    cout<<b<<endl;

反汇编:

  401247:    a1 04 20 44 00           mov    0x442004,%eax                //b的地址赋给eax寄存器
  40124c:    89 44 24 04              mov    %eax,0x4(%esp)
  401250:    c7 04 24 a0 53 44 00     movl   $0x4453a0,(%esp)
  401257:    e8 74 bd 02 00           call   42cfd0 <__ZNSolsEi>    

 

static int b的位置:

Disassembly of section .data:

00442000 <__data_start__>:
  442000:    45                       inc    %ebp
  442001:    00 00                    add    %al,(%eax)
    ...

00442004 <_ZZ4mainE1b>:
  442004:    03 00                    add    (%eax),%eax        //b的位置

 

从上面看出,static 变量的时候,该变量是存放在.data(可读写数据段)的,并不在栈内。

 

最后,对于不同的编译器,上面的描述可能会有些许不同

比如在用交叉编译工具arm-linux-gcc编译代码的时候

unsigned long 类型的数组是存放在只读数据段内的,

它需要先把数据先从.rodata读进寄存器r0-r3,然后再从r0-r3寄存器把数据写到栈内

如果该数组有大量数据,则会分开几次做数据传送。

posted @ 2012-12-19 02:36  TaigaComplex  阅读(815)  评论(0编辑  收藏  举报