c语言在内存中的分布

参考文章https://www.cnblogs.com/yif1991/p/5049638.html

存储时结构

首先写一个c语言版的Hello World

#include 
void main()
{
    printf("hello,world\n");
}

将该段程序写在一个hello.c的文件中。打开终端,编译hello.c生成一个a.out的文件。
c语言在内存中的分布_第1张图片

终端上展示的是一个c语言的可执行文件在不同的内存所占的空间,text、data、bss分别代表内存中不同的区域,dec代表十进制总和,hex代表着16进制总和,filename代表着文件名。

text 代码区。

  • 程序被操作系统加载到内存的时候,所有的可执行代码(程序代码指令、常量字符串等)都加载到代码区,这块内存在程序运行期间是不变的。代码区是平行的,里面装的就是一堆指令,在程序运行期间是不能改变的。函数也是代码的一部分,故函数都被放在代码区,包括main函数。

  • 注意:“int a = 0;”语句可拆分成”int a;”和”a = 0”,定义变量a的”int a;”语句并不是代码,它在程序编译时就执行了,并没有放到代码区,放到代码区的只有”a = 0”这句。

静态区

  • 该区包含了在程序中明确被初始化的全局变量、静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)
int x = 99;
static x = 100; 

未初始化数据区

  • BSS区(uninitialized data segment)
  • BSS区的数据在程序开始执行之前被内核初始化为0或者空指针(NULL)
int x;

以上为可执行代码存储时结构。

运行时结构

运行时结构多个栈区和堆区。

c语言在内存中的分布_第2张图片

栈(stack)

  • 一种先进后出的内存结构,局部变量(自动变量)和函数形式参数都存储在此,存储的这个动作由编译器自动完成,写程序时不需要考虑。
  • 栈区在程序运行期间是可以随时修改的。当一个自动变量超出其作用域时,自动从栈中弹出。
  • 每个线程都有自己专属的栈。
  • 栈的最大尺寸固定,超出则引起栈溢出。
  • 变量离开作用域后栈上的内存会自动释放。

下面这个例子可以看出地址分配规律。

#include 

int n = 0;
void test(int a, int b);

int main() {
    static int m = 0;
    int a = 0;
    int b = 0;
    printf("自动变量a的地址是:%d\n自动变量b的地址是:%d\n", &a, &b);
    printf("全局变量n的地址是:%d\n静态变量m的地址是:%d\n", &n, &m);
    test(a, b);
    printf("main函数的地址是:%d", &main);
}

void test(int x, int y)
{
    printf("形式参数x的地址是:%d\n形式参数y的地址是:%d\n",&x, &y);
}

运行结果如下:
c语言在内存中的分布_第3张图片

  • 由运行结果可以知道局部变量和形参地址相似,存储在一个内存区;全局变量和静态变量地址相似,存储在一个区。根据运行结果中a,b,x,y的地址值大小可以还原占内存的内存分配原理。局部变量按照执行顺序入栈,函数参数的入栈顺序是从右到左。

c语言在内存中的分布_第4张图片

内存与指针

#include 
int *getx()
{
    int x = 10;
    return &x;
}

int main()
{
    int *p = getx();

    printf("%d\n", *getx());
    printf("%d\n", *p);

    *p = 20;
    printf("%d\n", *p);
}

这段代码没有任何语法错误,编译也可以正常通过。因为int *p = getx()中变量x的作用域为getx()函数体内部,这里得到一个临时栈变量x的地址,getx()函数调用结束后这个地址就无效了。所以有关于这个地址的任何操作都不一定成立了。

  • 栈不会很大,一般都是以K为单位。如果在程序中直接将较大的数组保存在函数内的栈变量中,很可能会内存溢出,导致程序崩溃。比较大的内存就要用到堆(heap)内存了。

堆(heap)

  1. 堆是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。更重要的是堆是一个大容器,它的容量要远远大于栈,这可以解决上面实验三造成的内存溢出困难。一般比较复杂的数据类型都是放在堆中。
  2. 在C语言中,堆内存空间的申请和释放需要手动通过代码来完成。对于一个32位操作系统,最大管理管理4G内存,其中1G是给操作系统自己用的,剩下的3G都是给用户程序,一个用户程序理论上可以使用3G的内存空间。堆上的内存必须手动释放(C/C++)

malloc与free

  • malloc函数用来在堆中分配指定大小的内存,单位为字节(Byte),函数返回void *指针。
  • free负责在堆中释放malloc分配的内存。malloc与free一定成对使用。
#include 
#include "stdlib.h"
#include "string.h"
void print_array(char *p, char n)
{
    int i = 0;
    for (i = 0; i < n; i++)
    {
        printf("p[%d] = %d\n", i, p[i]);
    }
}

int main()
{
    char *p = (char *)malloc(1024*1024*1024);//在堆中申请了内存
    memset(p, 'a', 10);//初始化内存
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        p[i] = i + 65;
    }
    print_array(p, 10);
    free(p);//释放申请的堆内存
}

运行结果如下:
c语言在内存中的分布_第5张图片

改造占内存中那个以指针作为返回值的getx()的例子,改为申请堆内存,即可完成需求,但一定用通过free函数释放申请的堆内存空间。改造以后的函数如下:

#include 
#include "stdlib.h"
#include "string.h"
int *getx()
{
    int *p = (int *)malloc(sizeof(int));//申请了一个堆空间
    return p;
}


int main()
{
    int *pp = getx();
    *pp = 10;

    printf("%d\n", *pp);

    free(pp);
}

用来在堆中申请内存空间的函数还有calloc和realloc,用法与malloc类似。文章地址:https://www.cnblogs.com/lidabo/p/4611411.html

动态创建数组,则用堆。

#include 
#include "stdlib.h"

int main() {
    int len;
    int * arr;

    printf("请输入数组长度:");
    scanf("%d", &len);
    arr = (int *)malloc(sizeof(int)*len);

    printf("请输入数组的值:");
    for ( int i = 0; i < len; i ++) {
        scanf("%d", &arr[i]);
    }
    for (int j = 0; j < len; j ++) {
        printf("%d:%d ", j , arr[j]);
    }
    free(arr);
    return 0;
}

你可能感兴趣的:(c语言)