C语言中使用动态内存

在前面介绍C语言的内存模型时知道C语言中将内存划分为多个区间:栈区、堆区、静态区、常量区、代码区。在方法内定义和使用的变量,如果没有使用static关键字修饰都是在栈区内,该区内定义的变量不需要我们管理,系统会自动申请和释放空间。在内存空间划分中还有一个非常重要的堆区,在这个区间定义的变量需要程序自己管理空间的申请和释放,如果使用不当,就会造成内存泄露问题,严重的更可能导致程序崩溃。

C语言动态操作内存首先要引入 stdlib.h 库,涉及到内存操作的函数有下面这些:

(1)malloc:动态分配一块内存空间,该函数的形参是要分配的内存空间大小,单位是字节。函数的返回值是一个void类型的指针,在使用时需要转换为具体类型的指针。函数原型是:

c 复制代码
void * malloc(size_t _Size);

通过函数定义可以知道该函数只是返回内存的首地址,没有总内存大小,如果要在后面使用内存大小最好自己定义一个变量记录下来;malloc函数申请的空间也不会初始化,在赋值前访问内存空间是脏数据;申请过多的内存空间后,会产生虚拟内存;注意申请的内存要及时释放,避免内存泄露的风险。

函数使用示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 申请存放10个整型数据的内存
    int* p = (int *) malloc(10 * sizeof(int));

    // 遍历申请的内存空间输出数据
    printf("---------------- uninitialized ----------------\n");
    for (int i = 0; i < 10; i++) {
        printf("%d\n", *(p + i));
    }
    // 内存赋值
    for (int i = 0; i < 10; i++) {
        *(p + i) = (i * 10);
    }

    // 遍历申请的内存空间输出数据
    printf("---------------- initialization ----------------\n");
    for (int i = 0; i < 10; i++) {
        printf("%d\n", *(p + i));
    }

    // 释放内存空间
    free(p);

    return 0;
}

程序运行输出:

---------------- uninitialized ----------------
-1023344304
508
-1023344304
508
778857573
1886350710
1852795252
1917845619
1634887535
875976557
---------------- initialization ----------------
0
10
20
30
40
50
60
70
80
90

(2)calloc:也是用于动态分配内存,该函数两个形参分别是元素数量以及每个元素所占的字节大小。与malloc不同的是,它会在分配内存后初始化为0。由于有初始化的操作,整体来说性能不如malloc。函数原型是:

c 复制代码
void * calloc(size_t _NumOfElements, size_t _SizeOfElements);

函数使用示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 申请存放10个整型数据的内存
    int* p = (int *) calloc(10, sizeof(int));

    // 遍历申请的内存空间输出数据
    printf("---------------- uninitialized ----------------\n");
    for (int i = 0; i < 10; i++) {
        printf("%d\n", *(p + i));
    }
    // 内存赋值
    for (int i = 0; i < 10; i++) {
        *(p + i) = (i * 10);
    }

    // 遍历申请的内存空间输出数据
    printf("---------------- initialization ----------------\n");
    for (int i = 0; i < 10; i++) {
        printf("%d\n", *(p + i));
    }

    // 释放内存空间
    free(p);

    return 0;
}

程序运行输出:

---------------- uninitialized ----------------
0
0
0
0
0
0
0
0
0
0
---------------- initialization ----------------
0
10
20
30
40
50
60
70
80
90

(3)realloc:用于更改已经分配的内存大小,函数有两个形参,第一个是指向已经分配的内存地址指针,第二个参数是新的内存大小。函数的返回值也是一个void类型的指针。函数原型是:

c 复制代码
void * realloc(void *_Memory, size_t _NewSize);

函数使用示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    int size = 10;
    // 申请存放10个整型数据的内存
    int* p = (int *) malloc(size * sizeof(int));

    // 内存赋值
    for (int i = 0; i < size; i++) {
        *(p + i) = (i * 10);
    }

    // 遍历申请的内存空间输出数据
    printf("---------------- initialization ----------------\n");
    for (int i = 0; i < size; i++) {
        printf("%d\n", *(p + i));
    }

    // 内存扩容
    size = 12;
    int* p1 = (int *) realloc(p, size * sizeof(int));
    if (p1 == NULL) {
        // 扩容失败后需要手动释放原来的内存空间
        free(p);
    }

    // 遍历释放后的内存空间
    printf("---------------- after realloc ----------------\n");
    for (int i = 0; i < size; i++) {
        printf("%d\n", *(p1 + i));
    }

    // 释放内存空间
    free(p1);

    return 0;
}

程序运行输出:

c 复制代码
---------------- initialization ----------------
0
10
20
30
40
50
60
70
80
90
---------------- after realloc ----------------
0
10
20
30
40
50
60
70
80
90
1247569260
1549891169

调用realloc函数如果扩容成功,原内存空间会自动处理,如果只是在原内存空间继续追加大小,内存地址会继续使用;如果原内存空间不能继续扩容,该函数会重新申请一块内存空间,并将原空间数据复制到新内存空间中,原来的内存空间会释放。如果扩容失败,原来的内存空间不会释放,这时就需要手动释放原内存空间。

由于内存空间充足,几次调用扩容函数都是在原地址上追加:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    int size = 10;
    // 申请存放10个整型数据的内存
    int* p = (int *) malloc(size * sizeof(int));

    // 内存赋值
    for (int i = 0; i < size; i++) {
        *(p + i) = (i * 10);
    }
    printf("---------------- malloc ----------------\n");
    printf("malloc memory address : %p\n", p);
    for (int i = 0; i < size; i++) {
        printf("%d\n", *(p + i));
    }

    // 内存扩容
    int* p1 = (int *) realloc(p, (size + 400) * sizeof(int));
    if (p1 == NULL) {
        // 扩容失败后需要手动释放原来的内存空间
        free(p);
    }

    // 遍历释放后的内存空间
    printf("---------------- after realloc ----------------\n");
    printf("realloc memory address : %p\n", p);
    for (int i = 0; i < size; i++) {
        printf("%d\n", *(p1 + i));
    }

    // 释放内存空间
    free(p1);

    return 0;
}

程序运行输出:

---------------- malloc ----------------
malloc memory address : 0000028a23cb1650
0
10
20
30
40
50
60
70
80
90
---------------- after realloc ----------------
realloc memory address : 0000028a23cb1650
0
10
20
30
40
50
60
70
80
90

(4)free:用于释放动态分配的内存,函数的形参是指向要释放的内存地址。函数原型是:

c 复制代码
void free(void *_Memory);

free函数释放后的内存空间中的数据是脏数据,可能被其他程序使用或清空,读取释放后的内存数据没有意义。

函数使用示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 申请存放10个整型数据的内存
    int* p = (int *) malloc(10 * sizeof(int));

    // 内存赋值
    for (int i = 0; i < 10; i++) {
        *(p + i) = (i * 10);
    }

    // 遍历申请的内存空间输出数据
    printf("---------------- initialization ----------------\n");
    for (int i = 0; i < 10; i++) {
        printf("%d\n", *(p + i));
    }

    // 释放内存空间
    free(p);

    // 遍历释放后的内存空间
    printf("---------------- after free ----------------\n");
    for (int i = 0; i < 10; i++) {
        printf("%d\n", *(p + i));
    }

    return 0;
}

程序运行输出:

c 复制代码
---------------- initialization ----------------
0
10
20
30
40
50
60
70
80
90
---------------- after free ----------------
-2109137856
691
-2109144752
691
40
50
60
70
80
90

使用堆内存性能是不如栈内存的,并且还存在内存泄露的风险。但是有些场景是栈内存无法满足的,比如要存储很多数据的大数组,链表结构的数据。还有方法的返回值,虽然可以使用指针或static修饰变量返回,但他们都有局限性:使用指针需要在方法形参传递过去,要提前初始化好内存空间,这种方式不方便扩展;使用static修饰变量存放在全局数据区,会导致整个程序生命周期内该变量都存在,即使数据不使用了也不会删除,浪费内存空间,所以方法返回数据使用动态内存是一个很好的选择。

相关推荐
小麻侬2 分钟前
windows下,golang+vscode+delve 远程调试
开发语言·vscode·golang
代码驿站52011 分钟前
Lua语言的函数实现
开发语言·后端·golang
武昌库里写JAVA13 分钟前
Golang的代码压缩技术应用案例分析与研究实践
数据结构·vue.js·spring boot·算法·课程设计
李歘歘19 分钟前
Golang笔记——切片与数组
开发语言·笔记·后端·golang·go
Yicsr28 分钟前
在Visual Studio中编译.c文件和.cpp文件主要有哪些不同
java·c语言·visual studio
王子良.31 分钟前
Hadoop3.x 万字解析,从入门到剖析源码
大数据·开发语言·hadoop·经验分享·学习·开源
ALIKAOvO42 分钟前
Qt opencv_camera
开发语言·qt·opencv
raoxiaoya1 小时前
golang中的eval,goeval,govaluate
开发语言·后端·golang
究极无敌暴龙战神1 小时前
复习自用2
人工智能·算法·机器学习
CyberScriptor1 小时前
PHP语言的软件工程
开发语言·后端·golang