小白初识动态内存管理

动态内存分配

目前开辟空间的方法

int a = 1;//在栈空间上开辟一个int类型 (四个字节)
char arr[10] = {0};//在栈空间上开辟char类型的 10个字节的连续空间

这两种开辟空间的方式的特点

                空间开辟⼤⼩是固定的。

                数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整

缺点也很明显

编译时无法确定内存大小
读取用户输入的字符串,其长度在编译时是未知的,只能在运行时根据实际输入分配内存。

高效利用内存资源
静态分配的内存(如数组)在程序运行期间始终占用固定空间,即使不再使用。

对于空间的需求,我们在编程中的需要更灵活的方式。

有时候我们需要的空间⼤⼩在程序运⾏的时候才能知道,那数组的编译时开辟空间的⽅式就不能满⾜了。

动态内存分配特点

在编程中,我们经常需要根据程序运行时的需求来分配内存,而不是在编译时就固定内存大小。 

特点: 

处理大型数据结构
对于需要大量内存的结构,动态分配可以避免栈溢出,因为动态内存位于堆中,而不是栈上。 

动态分配可以在需要时申请,不需要时释放,提高内存利用率。

malloc 和 free:动态内存的基础操作

malloc 函数

定义:用于在堆上分配指定大小的内存块,返回指向该内存的指针。

函数原型:

void* malloc(size_t size);

参数:size 表示需要分配的字节数。


        成功:开辟成功,则返回⼀个指向开辟好空间的指针。
        失败:则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。

        void* :所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃⼰来决定。

        如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器。

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

int main() {
    // 分配一个整数大小的内存
    int* ptr = (int*)malloc(sizeof(int));
    if (ptr == NULL) {
        peorr(“malloc”)
        return 1;
    }
    
    // 使用内存
    *ptr = 100;
    printf(" %d\n", *ptr);  // 输出:100
    
    // 释放内存
    free(ptr);
    ptr = NULL;  // 避免野指针
    
    return 0;
}

 free 函数

定义:free 用于释放 malloccalloc 或 realloc 分配的内存,使其可被后续程序再次使用。

void free(void* ptr);

参数:ptr 是指向要释放内存的指针。

注意事项:必须传入由动态分配函数返回的指针,否则行为未定义。

释放后的指针应立即赋值为 NULL,避免成为野指针。 

calloc 和 realloc:进阶内存管理

 calloc 函数

定义:calloc 用于分配内存并初始化为零,适合需要初始化的场景(如数组、结构体)。

void* calloc(size_t num, size_t size);

参数:num:元素个数。

size:每个元素的大小(字节)。

返回值:        成功:返回指向分配并初始化内存的指针。

                       失败:返回 NULL。

// 分配包含5个整数的数组并初始化为0
int* arr = (int*)calloc(5, sizeof(int));
if (arr != NULL) {
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);  // 输出:0 0 0 0 0
    }
    free(arr);
}

和malloc函数相比,calloc函数会将动态分配的内存全部初始化,有利于编程的运用 

realloc 函数

定义:realloc 用于调整已分配内存块的大小,可能涉及内存搬迁。

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

参数:

        ptr:指向现有内存块的指针(可为 NULL,此时等价于 malloc)。

        new_size:新的内存大小(字节)。

返回值:

        成功:返回指向新内存块的指针(可能与原指针相同或不同)。

        失败:返回 NULL,原内存块保持不变。 

int* ptr = (int*)malloc(3 * sizeof(int));  // 初始分配3个整数
ptr = (int*)realloc(ptr, 5 * sizeof(int));  // 调整为5个整数
if (ptr != NULL) {
    // 内存已扩展,可以安全访问5个元素
    free(ptr);
}

注意事项: 

        

new_size 小于原大小,内存会被截断,数据可能丢失。

realloc在调整内存空间的是存在两种情况:

情况1:原有空间之后有⾜够⼤的空间

如果此时用realloc函数进行扩容,

要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化。

情况2:原有空间之后没有⾜够⼤的空间

如图如果要扩展的内存空间不够,

扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩的连续空间来使⽤。这样函数返回的是⼀个新的内存地址。

到一个新的空间进行内存分配

由于上述的两种情况,realloc函数的使⽤就要注意⼀些。

常见的动态内存错误

动态内存管理容易出错,以下是几种典型错误

内存泄漏

原因:分配的内存未被释放,导致可用内存逐渐减少。

void func() {
    int* ptr = malloc(100);
    // 忘记调用 free(ptr)
}  // 每次调用func都会泄漏100字节

解决方案:
        确保每个 malloc/calloc/realloc 都有对应的 free。
        使用 RAII(资源获取即初始化)思想,在函数退出前释放资源。 

野指针

原因:释放内存后仍使用该指针。

int* ptr = malloc(sizeof(int));
free(ptr);
*ptr = 10;  // 错误:访问已释放的内存

解决方案:
        释放内存后立即将指针置为 NULL。
        避免返回指向局部变量的指针(栈内存会在函数返回后释放)。 

重复释放

原因:多次释放同一块内存。

int* ptr = malloc(sizeof(int));
free(ptr);
free(ptr);  // 错误:重复释放

解决方案: 释放后将指针置为 NULL,因为 free(NULL) 无操作。 

越界访问

原因:访问超出分配内存范围的数据。

int* arr = malloc(3 * sizeof(int));
arr[3] = 10;  // 错误:越界访问(有效索引为0~2)

解决方案: 始终检查数组索引范围。

                   使用安全的内存操作函数(如 snprintf 替代 sprintf)。 

柔性数组

定义:C99 标准引入了柔性数组,允许结构体的最后一个成员为不指定大小的数组,用于动态扩展内存。

定义规则

        结构体至少包含一个其他成员。

        柔性数组成员必须是最后一个成员。

        声明时不指定大小,用 [] 表示。

事例

struct Buffer {
    int length;       // 必须有其他成员
    char data[];      // 柔性数组(无大小)
};

// 动态分配内存
struct Buffer* buf = (struct Buffer*)malloc(
    sizeof(struct Buffer) + 100 * sizeof(char)  // 额外分配100字节
);
buf->length = 100;
strcpy(buf->data, "Hello");

 优势

        内存连续,提高访问效率。

        一次性分配和释放,避免内存碎片。

注意事项

        必须通过 malloc 分配内存,且大小需包含柔性数组的预期空间。

        释放时只需一次 free

C/C++ 程序内存区域划分

        C/C++ 程序的内存通常分为以下几个区域:

栈区(Stack)

存储内容:局部变量、函数参数、返回地址等。

特点:     由编译器自动管理,函数调用时分配,返回时释放。

                内存分配和释放速度快,但空间有限(通常为几 MB)。

                按后进先出(LIFO)顺序管理内存

堆区(Heap)

存储内容:动态分配的内存(如 mallocnew)。

        特点:手动管理,需显式释放(freedelete)。

                   空间较大(受限于物理内存和虚拟内存),但分配和释放较慢。

                   容易出现内存碎片和泄漏。

全局 / 静态区(

存储内容:全局变量、静态变量(static)。

        特点:     程序启动时分配,程序结束时释放。

                        初始化的全局变量和静态变量存储在.data 段。

                        未初始化的存储在.bss 段(自动初始化为 0)

常量区

存储内容:字符串常量、const 变量等。

        特点:只读,修改会导致运行时错误。

                   通常与代码段共享物理内存,提高缓存效率。

代码段

存储内容:程序的可执行代码。

        特点:只读,防止程序被意外修改。

                   可共享(多个进程可同时运行同一程序)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值