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寄存器把数据写到栈内
如果该数组有大量数据,则会分开几次做数据传送。