C 语言中的内存管理

进程空间

进程和程序

  • 程序是静态的
  • 源码经过预处理、编译、汇编、链接变成可执行文件,该可执行文件可以认为就是程序
  • 可执行文件能够多次执行,但并不意味着每次使用的资源是一样的
  • 进程是动态的
  • 当程序加载到内存中开始运行,直到运行结束,这样从开始到结束的过程就是进程
  • 程序位于存储设备上,此时不叫做进程,当加载到内存上开始执行才转变为进程

进程空间图

内存的布局大概如同下图所示:

栈内存(stack)

  • 栈中能够存放任意类型的变量
  • 变量必须是用 auto 修饰符修饰的
  • 栈内存随用随开,用完即销
  • 一切都由系统完成,不需手动操作
  • 栈有固定的大小,不同的平台可能不一致
  • 使用时要注意不要出现栈溢出(局部变量太多)

堆内存(heap)

  • 堆内存中能够存放任意类型的数据
  • 需要自己申请和释放
  • 堆没有固定的大小,但会受制于硬件条件和当前内存的连续性

堆内存的操作函数

malloc

函数声明为:

void *malloc( size_t size );

函数功能:申请堆内存空间并返回

参数:

  • size:表示要申请的字符数
  • void *:成功返回非空指针指向申请的空间,失败返回 NULL

calloc

函数声明为:

void *calloc( size_t num, size_t size );

函数功能:申请堆内存空间并返回,所申请的空间,自动赋值为 0 

参数:

  • num:所需内存单元的数量
  • size:内存单元的大小
  • void *:成功返回非空指针指向申请的空间,失败返回 NULL

realloc

函数声明为:

void *realloc( void *ptr, size_t size );

函数功能:扩容原有内存的大小

参数:

  • ptr:表示要扩容的指针(之前用 malloc 或 calloc 分配的内存地址),如果为 NULL,就相当于 malloc
  • size:表示扩容后内存的大小
  • void *:成功就返回非空指针指向的空间,失败返回 NULL。如果原空间后有足够的空间,就返回相同地址;若后续空间不足,则返回重新申请空间的地址。

free

函数声明为:

void free( void *ptr );

函数功能:释放申请的堆内存

参数:

  • ptr:指向之前申请的堆内存
  • void:无返回值

函数的使用可以看下边的程序:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a[10] = {1,2,3,4,5,6,7,8,9,10};

    printf("a = %p\n",a);
    for (int i = 0; i < 10; i++)
        printf("a[%d] = %d\n",i,a[i]);
    printf("\\***********************\\\n");

    int *pa = malloc(sizeof(int) * 10);

    if (NULL == pa)
    {
        printf("Heap alloc error!");
        return -1;
    }

    printf("&pa = %p,pa = %p,*pa = %d\n",&pa,pa,*pa);
    for (int i = 0; i < 10; i++)
        printf("*(pa + %d) = %d\n",i,*(pa + i));

    free(pa);

    printf("\\***********************\\\n");

    pa = (int *)malloc(sizeof(int) * 10);

    if (NULL == pa)
    {
        printf("Heap alloc error!");
        return -1;
    }

    printf("&pa = %p,pa = %p,*pa = %d\n",&pa,pa,*pa);
    for (int i = 0; i < 10; i++)
        printf("*(pa + %d) = %d\n",i,*(pa + i));

    free(pa);

    printf("\\***********************\\\n");

    pa = (int *)calloc(10,sizeof(int));

    if (NULL == pa)
    {
        printf("Heap alloc error!");
        return -1;
    }

    printf("&pa = %p,pa = %p,*pa = %d\n",&pa,pa,*pa);
    for (int i = 0; i < 10; i++)
        printf("*(pa + %d) = %d\n",i,*(pa + i));

    free(pa);

    printf("\\***********************\\\n");

    pa = (int *)calloc(10,sizeof(int));

    if (NULL == pa)
    {
        printf("Heap alloc error!");
        return -1;
    }

    printf("&pa = %p,pa = %p,*pa = %d\n",&pa,pa,*pa);
    for (int i = 0; i < 10; i++)
        printf("*(pa + %d) = %d\n",i,*(pa + i));

    pa = (int *)realloc(pa,sizeof(int) * 10);

    if (NULL == pa)
    {
        printf("Heap alloc error!");
        return -1;
    }

    printf("&pa = %p,pa = %p,*pa = %d\n",&pa,pa,*pa);
    for (int i = 0; i < 20; i++)
        printf("*(pa + %d) = %d\n",i,*(pa + i));

    free(pa);

    printf("\\***********************\\\n");

    pa = (int *)calloc(10,sizeof(int));

    if (NULL == pa)
    {
        printf("Heap alloc error!");
        return -1;
    }

    printf("&pa = %p,pa = %p,*pa = %d\n",&pa,pa,*pa);
    for (int i = 0; i < 10; i++)
        printf("*(pa + %d) = %d\n",i,*(pa + i));

    pa = (int *)realloc(pa,sizeof(int) * 1000);

    if (NULL == pa)
    {
        printf("Heap alloc error!");
        return -1;
    }

    printf("&pa = %p,pa = %p,*pa = %d\n",&pa,pa,*pa);
    for (int i = 0; i < 20; i++)
        printf("*(pa + %d) = %d\n",i,*(pa + i));

    free(pa);

    printf("\\***********************\\\n");

    return 0;
}

结果为:

结果为:

a = 0060FE68
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
a[5] = 6
a[6] = 7
a[7] = 8
a[8] = 9
a[9] = 10
\***********************\
&pa = 0060FE64,pa = 00FD16F0,*pa = 16594704
*(pa + 0) = 16594704
*(pa + 1) = 16580800
*(pa + 2) = 16590056
*(pa + 3) = 16580800
*(pa + 4) = 1835888451
*(pa + 5) = 1547136623
*(pa + 6) = 1819242324
*(pa + 7) = 1459641459
*(pa + 8) = 1229213257
*(pa + 9) = 977485138
\***********************\
&pa = 0060FE64,pa = 00FD16F0,*pa = 16594704
*(pa + 0) = 16594704
*(pa + 1) = 16580800
*(pa + 2) = 16590056
*(pa + 3) = 16580800
*(pa + 4) = 1835888451
*(pa + 5) = 1547136623
*(pa + 6) = 1819242324
*(pa + 7) = 1459641459
*(pa + 8) = 1229213257
*(pa + 9) = 977485138
\***********************\
&pa = 0060FE64,pa = 00FD16F0,*pa = 0
*(pa + 0) = 0
*(pa + 1) = 0
*(pa + 2) = 0
*(pa + 3) = 0
*(pa + 4) = 0
*(pa + 5) = 0
*(pa + 6) = 0
*(pa + 7) = 0
*(pa + 8) = 0
*(pa + 9) = 0
\***********************\
&pa = 0060FE64,pa = 00FD16F0,*pa = 0
*(pa + 0) = 0
*(pa + 1) = 0
*(pa + 2) = 0
*(pa + 3) = 0
*(pa + 4) = 0
*(pa + 5) = 0
*(pa + 6) = 0
*(pa + 7) = 0
*(pa + 8) = 0
*(pa + 9) = 0
&pa = 0060FE64,pa = 00FD16F0,*pa = 0
*(pa + 0) = 0
*(pa + 1) = 0
*(pa + 2) = 0
*(pa + 3) = 0
*(pa + 4) = 0
*(pa + 5) = 0
*(pa + 6) = 0
*(pa + 7) = 0
*(pa + 8) = 0
*(pa + 9) = 0
*(pa + 10) = -317360001
*(pa + 11) = 60758
*(pa + 12) = 16594704
*(pa + 13) = 16580800
*(pa + 14) = -1424721864
*(pa + 15) = 134278482
*(pa + 16) = 2
*(pa + 17) = 936
*(pa + 18) = 1
*(pa + 19) = 2052
\***********************\
&pa = 0060FE64,pa = 00FD16F0,*pa = 0
*(pa + 0) = 0
*(pa + 1) = 0
*(pa + 2) = 0
*(pa + 3) = 0
*(pa + 4) = 0
*(pa + 5) = 0
*(pa + 6) = 0
*(pa + 7) = 0
*(pa + 8) = 0
*(pa + 9) = 0
&pa = 0060FE64,pa = 00FD3710,*pa = 0
*(pa + 0) = 0
*(pa + 1) = 0
*(pa + 2) = 0
*(pa + 3) = 0
*(pa + 4) = 0
*(pa + 5) = 0
*(pa + 6) = 0
*(pa + 7) = 0
*(pa + 8) = 0
*(pa + 9) = 0
*(pa + 10) = 0
*(pa + 11) = 0
*(pa + 12) = 0
*(pa + 13) = 0
*(pa + 14) = 0
*(pa + 15) = 0
*(pa + 16) = 0
*(pa + 17) = 0
*(pa + 18) = 0
*(pa + 19) = 0
\***********************\

从上边的结果可以看出:

  • malloc 分配的内存中,对应的值是随机的
  • 利用 alloc 函数分配的内存为了正常使用,一般要经历分配、判空、使用、释放四个阶段
  • 一般情况下,利用 alloc 函数分配的内存为了不混淆,最好是采用 (datatype *)xxalloc 的形式
  • free 掉的内存还可以进行重新分配
  • 释放掉的只是内存,指针并没有被清除,指针的存储位置也没有改变,定义的指针变量是符合变量作用域和生命周期的
  • calloc 分配的内存中,对应的值会被初始化为 0
  • realloc 分配的内存,如果是连续扩容的,返回的地址与之前的源地址相同,扩容的内存对应的值为随机值
  • realloc 分配的内存,如果是重新分配的,返回的地址与之前的源地址不同,扩容的内存对应的值为 0
  • 上面的程序中 &pa 对应的是指针的存储位置,pa 对应的是分配内存的指针,*pa 对应的是该指针处的 datatype 的值
  • void * 代表的是单字节地址,所以才会在分配内存时要求加上 (datatype *)

几个常见问题

内存使用完毕后一定要释放

因为内存大小受限于硬件,为了合理使用,保证计算机能够以最优的状态运行,内存使用完毕后一定进行释放操作,不然会造成内存泄露,拖慢计算机的运行速度。

内存申请释放后最好置 NULL

上边的程序说明申请的内存是能够释放的,但是指向这段内存的指针是没有被清除的,因此,如果在释放内存已经是内存的结束部分或者并没有什么特殊的操作的话,程序写到这里就已经没有问题了。

但是如果内存释放掉还需要对该指针进行操作,那么就最好先将该指针置 NULL,再编写剩余的代码,不然会造成内存的非法使用。

看下边的程序:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *pa = (int *)calloc(10,sizeof(int));

    if (NULL == pa)
    {
        printf("Heap alloc error!");
        return -1;
    }

    for (int i = 0;i < 10; i++)
        printf("*(pa + %d) = %d\n",i,*(pa + i));

    free(pa);

    putchar(10);

    for (int i = 0;i < 10; i++)
        printf("*(pa + %d) = %d\n",i,*(pa + i));

    pa = NULL;

    return 0;
}

结果为:

*(pa + 0) = 0
*(pa + 1) = 0
*(pa + 2) = 0
*(pa + 3) = 0
*(pa + 4) = 0
*(pa + 5) = 0
*(pa + 6) = 0
*(pa + 7) = 0
*(pa + 8) = 0
*(pa + 9) = 0

*(pa + 0) = 16463632
*(pa + 1) = 16449728
*(pa + 2) = 0
*(pa + 3) = 0
*(pa + 4) = 0
*(pa + 5) = 0
*(pa + 6) = 0
*(pa + 7) = 0
*(pa + 8) = 0
*(pa + 9) = 0

从上边的结果可以看出:

  • 内存释放之后指针仍然有效
  • 可以借由指针访问已经被释放掉的内存
  • 只是单纯的读操作好像不会有什么大问题,但是如果要修改其中内容的话,就不知道会发生什么了
  • 为了防止误操作,可以使用 pa = NULL 作为内存的一种保护机制

谁申请谁释放

在进行程序开发,尤其是协同开发的时候,内存申请最好做到谁申请谁释放,或者建立管理机制,不要造成不释放或者重复释放的情况。

alloc 与 free 配对使用

这句话有两层含义:

  • 要记得 alloc 之后一定要进行 free 操作
  • free 不能用来释放别的内存空间,只能与 alloc 配对使用
发布了77 篇原创文章 · 获赞 5 · 访问量 4866

猜你喜欢

转载自blog.csdn.net/SAKURASANN/article/details/104529828