有时候编写代码,总有一些程序编译能通过,但是一运行,就会抛错:
Segmentation fault,一直迷迷糊糊的,在对c程序内存布局了解以后,只要再出现这样的错误,就可以回想一下c程序内存布局的图,一定能解决!
各布局分区说明:
内存分布 | 说明 |
---|---|
System Space | 这段高地址内存大小为1GB,固定留给内核使用,称之为内核空间。 |
命令行参数区 | 在Linux操作系统中,我们在编写程序运行时,往往需要我们根据情况的不同,从命令行传入不同的参数,例如在网络socket编程中,我们可以利用getopt_long() 函数来获取命令行参数的值,如ip地址,端口或是域名 |
栈区(Stack) | 存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈,栈中的数据如果未初始化,则默认为随机值,从上至下分配,在其所属的 { } 内有效,离开 { } 失效。 |
堆区(Heap) | 一般由程序员分配和释放,若程序员不释放,程序运行结束时由操作系统回收。malloc()、calloc()、free() 等函数操作的就是这块内存,默认为0或是随机值。 |
. bss区 | 在数据段中,. bss区存储了下面这些变量:未初始化或者初始化为0的全局变量或者静态变量(static) |
. data区 | 同在数据段中,. data区存储了:初始化为非0的全局变量或静态变量(static) |
. rodata区 | ro 即 read only ,#define,char *ptr="string"等定义的数据常量,这段内存空间的值为 只读,这段内存上的数据受系统保护,如果尝试更改,就会出现 Segmentation Fault |
代码段 | 存放函数体的二进制代码。一个C语言程序由多个函数构成,C语言程序的执行就是函数之间的相互调用。 |
!!通过代码可以更深入的了解图与表中的概念!!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int g_var1;
int g_var2 = 50;
void sum(void);
int main(int argc,char **argv)
{
int a = 10;
int i;
static int c = 30;
char *str = "Hello";
char arr[] = "World";
char *ptr = NULL;
ptr = malloc(100);
strcpy(ptr,"goodbye");
printf(" Address of str: %p, Address of \"Hello\": %p\n",&str,str);
printf(" Address of g_var1: %p,g_var1 = %d, Address of g_var2: %p\n",&g_var1,g_var1,&g_var2);
printf(" Address of a: %p, Address of i: %p,i = %d\n",&a,&i,i);
printf(" Address of c: %p\n",&c);
printf(" Address of ptr:%p, Address of \"goodbye\":%p\n",&ptr,ptr);
printf(" Address of argv[0] :%p\n",&argv[0]);
for(i = 0;i < 10;i++)
sum();
free(ptr);
return 0;
}
static int g_var3;
void sum(void)
{
int total = 0;
static int s_cnt;
static int s_var1 = 40;
total++;
s_cnt++;
printf("total:%d s_cnt:%d\n",total,s_cnt);
}
执行代码:
从代码与运行结果中,可以清楚地看到所有类型的变量以及他们的地址,结合图分析他们的位置关系。
几个经典重点分析:
1.指针与字符串
char *str = "Hello";
可以通过运行结果看出,指针本身的地址距离指针指向地址,即,字符串首地址相差很远(Address of str: 0x7ffdf046e0d0, Address of “Hello”: 0x400848);而str就位于较高地址的栈中,字符串位于低地址的.rodata区中,受系统保护不可更改,若尝试更改:
char *str = "Hello";
str + 1 = 'h';
结果:
Segmentation fault (core dumped)
ps:数组可以更改,因为数组本身就存储在栈的,他的每个项都是都有自己的空间,是可以更改的,这也是数组与指针的不同点之一。
2.栈中数据的特性
从下面的运行结果看到,total在自加10次后,结果为1,但是用static定义的s_cnt确为10,这还是要从上面的表中找到答案:
void sum(void)
{
int total = 0;
static int s_cnt;
static int s_var1 = 40;
total++;
s_cnt++;
printf("total:%d s_cnt:%d\n",total,s_cnt);
}
total 为局部变量,存储在栈中,栈中数据只在其本身存在的 { } 中有效,而离开 { } 失效,所以每次函数运行结束该变量就失效了,在下一次循环开始后,有重新定义,分配;
而 s_cnt 是由 static 定义的变量,未初始化,默认值为0;存储在 .
bss区中,且数据段中的内容在程序运行期间始终有效,所以,这个变量每次自加后会存储在了他的.bss 区中,下次循环又从该位置取出自加;
关于其他的地址,可对照图标与程序进行比较。