为什么会想到了解C程序的储存空间布局呢,其实是因为在写代码的时候犯了一个很2的错误。悲催代码如下:
#include <stdio.h>
#include <string.h>
void func(char *s)
{
strcpy(s,"123");
printf("%s \n",s);
}
int main(int argc, const char *argv[])
{
char a[10];
func(a);
//func("asss");
return 0;
}
实际的程序很长,func中的操作也比较多,所以在编程过程中老是得不到正确的值,为了测试数据,本来应该给数组a 赋值的。 悲剧之处就在于直接写成了注释部分。导致了内存错误,然后就在func中猛找错误,后来定位到strcpy 这一句,最后才发现原来是参数的问题... 后来突然想到一个问题,如何在函数内部判断传入的参数是在堆中分配的还是在栈中呢。好吧,那就先要了解C程序的存储空间布局。主要参考是《APUE》。 C程序的存储空间有5部分: 正文段:由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是频繁执行的程序(如文本编辑器、C编译器和shell等)在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其自身的指令。 初始化数据段:通常将此段称为数据段,它包含了程序中需明确地赋初值的变量,初始化的全局变量和初始化的静态变量。 非初始化数据段:通常将此段称为bss段,这一名称来源于一个早期的汇编运算符,意思是“block started by symbol”(由符号开始的块),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。出现在任何函数之外的C声明。 栈:自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次调用函数时,其返回地址以及调用者的环境信息(例如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,可以递归调用C函数。递归函数每次调用自身时,就使用一个新的栈帧,因此一个函数调用实例中的变量集不会影响另一个函数调用实例中的变量。 堆:通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于非初始化数据段和栈之间。
贴一张空间分布图
学习了理论知识之后,再参照自己的系统看看。写段代码看看是不是这样。
#include <stdio.h>
#include <string.h>
int a = 10;
static int b = 10;
const int c = 10;
int d;
int main(int argc, const char *argv[])
{
int e = 0;
static int f = 0;
const int g = 0;
int h;
int *p;
p = malloc(10);
printf("hello, world \n");
printf("a %08x \n",&a);
printf("b %08x \n",&b);
printf("c %08x \n",&c);
printf("d %08x \n",&d);
printf("e %08x \n",&e);
printf("f %08x \n",&f);
printf("g %08x \n",&g);
printf("h %08x \n",&h);
printf("p %08x \n",p);
pause();
return 0;
}
运行结果如下:
用ps 命令找出这段代码运行的进程号为5571。进入/proc/5571/ 查看其中的maps文件,此文件是由内核填写的程序的运行空间分布情况。我参看了一些网上的其他文章,在正文段和数据段只有两行,而在我的机器上有三行。 第一行,这一行为程序的正文段,权限为r-x,符合理论中正文段通常为只读,以防止意外被修改。除了只读还有执行权限,想来也是,没有执行权限,怎么执行呢。p代表私有,s代表公有。 第二行, 这一行为只读数据段,权限为r--。 第三行, 读写数据段,这一行权限为rw-。 第四行是堆的起始地址,后面接这是库和内核虚拟的区域vdso,最后一行是栈的起始地址。栈是向沿地址向下生长的,从图中看就是向上走。 还有几处值得注意的是 b 和 f 虽然一个在函数外一个在函数内定义赋值,但是由于有static 修饰,都是分配在rw-数据段中。而同样是都有const修饰的 c 和 g 一个在r--数据段中,一个在栈中。其他的地址,可参照上面的理论对应。 回到最初的问题上,如何在函数的内部判断参数是在哪儿分配的呢,现在终于有一个思路了,拿参数的地址和对应的maps文件中的地址比较即可得出参数的存储位置了。