C语言——内存四区

之前在做C语言中关键字extern和static的笔记的时候,对于内存四区进行过简单的解释,这里再补充写一下自己对于内存四区的理解。

内存四区主要是指栈区、堆区、全局区、代码区,以下是关于各区域的定义。
栈区(stack): 编译器在需要的时候进行分配,在不需要的时候自动清除。里面的变量通常是函数的返回地址、参数、局部变量、返回值等。所以,经常会出现参数的入栈和出栈说法。

堆区(heap): 一般由程序员分配和释放,例如c语言中的malloc和free以及C++中的new和delete。【切记如果采用了堆区,在用完一定需要释放】

全局区(静态区): 全局变量和静态变量被分配到同一块内存中。(存放字符串常量,全局变量)

代码区: 可以简单理解为存放代码的区域。

在展示案例分析的时候,先记录以下程序的编译阶段。
当我们写好一个c++或者c语言的代码的时候,在运行程序前会经过预处理、编译、汇编、链接四部分操作。

预处理阶段: 展开宏定义、头文件。这时候得到的还是一个文本文件。
编译阶段: 将预处理阶段得到的.i文件转换成汇编语言,汇编语言程序中的每条语句都以一种标准的文本格式确切的描述了一条低级机器语言指令,所以有些人从事反汇编的工作,通过汇编得到的东西分析恶意软件、闭源软件的漏洞、写外挂等。

汇编阶段: 将编译得到的汇编.s文件,翻译成机器语言指令,把这些指令打包成一种可重定位目标程序的格式,并将结果保存在目标文件(二进制)中。

链接阶段: 将目标文件链接成可执行程序。
当执行可执行文件的时候,如果逐行分析代码可以将系统分成四个区域来理解程序。

下面通过网上找的几个样例程序来理解一下内存四区的知识:

//ubuntu 16.04 gcc编译
#include <stdio.h>
char* str_func()
{
    char *p = "hello,world";
    printf("str_func()中p:%s\n",p);
    return p;
}

void test01()
{
    char *p = NULL;
    p = str_func();
    printf("test01()中p:%s\n",p);
}

int main()
{
    test01();
    return 0;
}

int main()
{
    test01();
    return 0;
}

输出结果:
在这里插入图片描述
在这里插入图片描述
如图中所示,程序中的hello,world字符串存放在全局区,不是栈区,所以函数test01中,打印仍然能够输出字符串。
这里str_func函数中的指针p,首先指向的是hello,world的字符串地址,当函数str_func函数执行完毕之后,返回的p的值就是字符串的地址,这个值被函数test01中的指针p接住,由于全局区不想栈区使用完毕会被回收,所以这里再次打印字符串,仍然能够打印出hello,world。

//ubuntu 16.04 gcc编译
#include <stdio.h>

char* str_func()
{
    char p[] = "hello,world";
    printf("str_func()中p:%s\n",p);
    return p;
}

void test01()
{
    char *p = NULL;
    p = str_func();
    printf("test01()中p:%s\n",p);
}

int main()
{
    test01();
    return 0;
}

输出结果:
在这里插入图片描述
这里采用gcc进行编译的时候,会提示一个警告,内容如下:
function returns address of local variable [-Wreturn-local-addr]
return p;
也就是说在函数str_func()中,返回的是一个局部变量,但是C语言只报了警告,没有报错,这是因为C语言不会检查越界的情况,或者可以说C语言允许越界。
下面来分析一下内存四区图,解释一下为什么,输出结果中test01打印为空。
在这里插入图片描述

由于采用函数str_func中是采用数组来接全局区的字符串,这里将全局区的字符串直接拷贝复制到数组p[ ]中,在函数test01中接到返回值是数组的首地址,本来确实应该成功打印字符串,但是str_func函数执行完,所有函数中的局部变量就被销毁了,地址对应的值就不可能在是字符串hello,world.

从上面的例子可以简单的理解函数的四区问题,下面说一下编译器对于字符串的存放。

ubuntu 16.04 gcc编译
#include <stdio.h>
int main()
{
    char p1 = "hello,world";
    char p2 = "hello,world";
    char q = "hello,world!";
    printf("p1的地址:%p\n", p1);
    printf("p2的地址:%p\n", p2);
    printf("q的地址:%p\n", q);
    return 0;
}

输出结果:
在这里插入图片描述

结果表明在特定的编译器下面,存放在全局区的相同的字符串输出的地址是一致的。造成这个的主要原因是编译器对代码进行了优化,减少了存放的空间。


版权声明:本文为weixin_42963375原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。