全局变量和全局常量的实验
延续之前的方式,给出一个简单的C程序,其中声明的全局变量分为3种:
初始化过的全局变量
未初始化的全局变量
全局常量
#vi test5.c
int i=1;
int j=2;
int k=3;
int l,m;
int n;
const int o=7;
const int p=8;
const int q=9;
int main()
{
l=4;
m=5;
n=6;
return i+j+k+l+m+n+o+p+q;
}
# gcc test5.c -o test5
# mdb test5
Loading modules: [ libc.so.1 ]
> main::dis
main: pushl %ebp ; main至main+1,创建Stack Frame
main+1: movl %esp,%ebp
main+3: subl $8,%esp
main+6: andl $0xf0,%esp
main+9: movl $0,%eax
main+0xe: subl %eax,%esp ; main+3至main+0xe,为局部变量预留栈空间,并保证栈16字节对齐
main+0x10: movl $4,0x8060948 ; l=4
main+0x1a: movl $5,0x806094c ; m=5
main+0x24: movl $6,0x8060950 ; n=6
main+0x2e: movl 0x8060908,%eax
main+0x33: addl 0x8060904,%eax
main+0x39: addl 0x806090c,%eax
main+0x3f: addl 0x8060948,%eax
main+0x45: addl 0x806094c,%eax
main+0x4b: addl 0x8060950,%eax
main+0x51: addl 0x8050808,%eax
main+0x57: addl 0x805080c,%eax
main+0x5d: addl 0x8050810,%eax ; main+0x2e至main+0x5d,i+j+k+l+m+n+o+p+q
main+0x63: leave ; 撤销Stack Frame
main+0x64: ret ; main函数返回
现在,让我们在全局变量初始化后的地方设置断点,观察一下这几个全局变量的值:
> main+0x2e:b ; 设置断点
> :r ; 运行程序
mdb: stop at main+0x2e
mdb: target stopped at:
main+0x2e: movl 0x8060908,%eax
> 0x8060904,03/nap ; 察看全局变量 i,j,k的值
test5`i:
test5`i:
test5`i: 1
test5`j: 2
test5`k: 3
> 0x8060948,03/nap ; 察看全局变量l,m,n的值
test5`l:
test5`l:
test5`l: 4
test5`m: 5
test5`n: 6
> 0x8050808,03/nap ; 察看全局变量o,p,q的值
o:
o:
o: 7
p: 8
q: 9
>
概念:进程地址空间 Process Address Space
+----------------------+ ----> 0xFFFFFFFF (4GB)
| |
| Kernel Space |
| |
+----------------------+ ----> _kernel_base (0xE0000000)
| |
| Other Library |
: :
: :
| |
+----------------------+
| data section |
| Lib C Library |
| text section |
: :
: :
+----------------------+
| |
| |
: :
: grow up :
: :
| User Heap |
| |
+----------------------+
| bss |
| |
| User Data |
| |
+----------------------+
| |
| User Text |
| |
| |
+----------------------+ ----> 0x08050000
| |
| User Stack |
| |
: grow down :
: :
: :
| |
| |
+----------------------+ ----> 0
图 3-1 Solaris在IA32上的进程地址空间
如图3-1所示,Solaris在IA32上的进程地址空间和Linux是相似的,在用户进程的4GB地址空间内:
Kernel总是映射到用户地址空间的最高端,从宏定义_kernel_base至0xFFFFFFFF的区域
用户进程所依赖的各个共享库紧接着Kernel映射在用户地址空间的高端
最后是用户进程地址空间在地址空间的低端
各共享库的代码段,存放着二进制可执行的机器指令,是由kernel把该库ELF文件的代码段map到虚存空间,属性是read/exec/share
各共享库的数据段,存放着程序执行所需的全局变量,是由kernel把ELF文件的数据段map到虚存空间,属性为read/write/private
用户代码段,存放着二进制形式的可执行的机器指令,是由kernel把ELF文件的代码段map到虚存空间,属性为read/exec
用户代码段之上是数据段,存放着程序执行所需的全局变量,是由kernel把ELF文件的数据段map到虚存空间,属性为 read/write/private
用户代码段之下是栈(stack),作为进程的临时数据区,是由kernel把匿名内存map到虚存空间,属性为read/write/exec
用户数据段之上是堆(heap),当且仅当malloc调用时存在,是由kernel把匿名内存map到虚存空间,属性为read/write/exec
注意Stack和Heap的区别和联系:
相同点:
1. 都是来自于kernel分配的匿名内存,和磁盘上的ELF文件无关
2. 属性均为read/write/exec
不同点:
1.栈的分配在C语言层面一般是通过声明局部变量,调用函数引起的;堆的分配则是通过显式的调用(malloc)引起的
2.栈的释放在C语言层面是对用户透明的,用户不需要关心,由C编译器产生的相应的指令代劳;堆则需显式的调用(free)来释放
3.栈空间的增长方向是从高地址到低地址;堆空间的增长方向是由低地址到高地址
4.栈存在于任何进程的地址空间;堆则在程序中没有调用malloc的情况下不存在