Segmentation Fault ? Linux中C程序内存布局(透过代码看本质)

有时候编写代码,总有一些程序编译能通过,但是一运行,就会抛错:
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 区中,下次循环又从该位置取出自加;

关于其他的地址,可对照图标与程序进行比较。

发布了9 篇原创文章 · 获赞 12 · 访问量 812

猜你喜欢

转载自blog.csdn.net/weixin_45121946/article/details/104752775