【C语言】动态内存管理

1. 为什么要有动态内存分配

相信同学们通过前面的学习知道,在C语言中有以下的内存开辟⽅式:

比如:①创建一个变量

cpp 复制代码
int a = 10;//在栈空间上申请四个字节的空间

创建一个数组

②一个数组是一块连续的内存空间

cpp 复制代码
int arr[10] = {0};//在栈空间上开辟10个字节的连续空间

但是上述的开辟空间的⽅式有两个特点:

• 空间开辟大小是固定的。

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

举一个具体的例子:

cpp 复制代码
int arr[100] = {0};

这里给了我们一个能够存放100个整型的连续内存空间,那么如果我们有50个整型类型的数据要储存,那么就要浪费剩余的内存空间 ,如果我们有200个整型类型的数据要存储,那么arr数组的空间又不够我们存放数据

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小

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

于是C语言引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。

那么本期博客就给同学们学习在动态开辟上所用到的几个函数

2. malloc和free

2.1 malloc

C语⾔提供了⼀个动态内存开辟的函数:

函数原型:malloc - C++ Reference (cplusplus.com)

cpp 复制代码
void* malloc (size_t size);

参数:

size -- 内存块的大小,以字节为单位,类型是无符号整型(size_t)

功能:这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。

对于返回类型也做了介绍:

从函数原型也能看出:返回值的类型是 void* ,

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

另外如果malloc函数开辟失败的话,则返回⼀个 NULL 指针。

因此malloc的返回值⼀定要做检查!

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

2.2 free

C语言提供了另外⼀个函数free,专门是⽤来做动态内存的释放和回收的,函数原型如下:
free - C++ Reference (cplusplus.com)

cpp 复制代码
void free (void* ptr);//传过去是要释放的空间的起始地址

free函数⽤来释放动态开辟的内存。

• 如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。

• 如果参数 ptr 是NULL指针,则函数什么事都不做。

malloc和free都声明在 stdlib.h头⽂件中。

这里补充一点:

如果malloc函数开辟失败的话,我们特别想知道malloc开辟失败的原因是什么,

我们可以使用库函数 perror ,这个函数会直接打印出malloc开辟失败所对应错误信息。

我们一起看一下这个函数原型:perror - C++ Reference (cplusplus.com)

cpp 复制代码
void perror ( const char * str );

功能:打印错误信息

参数:

str -- 是一个字符串,包含了一个自定义消息,将显示在原本的错误消息之前。

返回值:无返回值

举个例⼦:

我们来看看malloc 开辟空间的具体使用方法:

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

int main()
{
	//开辟内存
	int* ptr = (int*)malloc(40);
	int* p = ptr;

	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

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

	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}

	//内存释放
	free(ptr);
	ptr = NULL;
	return 0;
}

代码解释如下:

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

int main()
{
	int* ptr = (int*)malloc(40);//malloc 函数会返回所申请的内存块的起始地址,返回类型为void*
	//将其强制类型转换为int*意思是所申请的这块空间之后将以整数对其进行访问操作
	int* p = ptr;//因为malloc要返回申请空间的起始地址,所以通常不直接对ptr操作,而是另外创建
	//一个指针存放ptr,后续对p进行操作
	if (p == NULL)
	{
		//malloc函数在申请内存空间时,若空间大小不够,会返回一个空指针
		//此时内存空间就会申请失败
		perror("malloc:");//打印程序错误信息,双引号内的内容由自己定
		//这里perror函数刚好可以验证空间是否申请成功
		return 1;//主函数中return 1表示程序异常结束
	}
	
    //代码走到这里,说明malloc成功开辟空间,我们在空间里赋值,
    //通过循环将 0 到 9 依次存储到分配的内存空间中。
    //这里使用了我们学习过指针偏移的方式 *(p + i) 来进行赋值。
    int i = 0;
	for (i = 0; i < 10; i++)//申请的空间大小为40个字节,转换为整形即10个整形
	{
		*(p + i) = i;
	}

    //同样通过指针偏移的方式读取并打印出存储在内存中的值。
    for (i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i));
    }

	free(ptr);//申请的这块内存空间使用结束后,需要释放这片空间,free函数的参数
    为申请空间的起始地址
	ptr = NULL;//空间释放后,ptr仍然指向空间内的一个有效地址,为避免后续对其使用造成野指针的情况
	
	return 0;
}

运行结果:

3. calloc

C语⾔还提供了⼀个函数叫 calloc , calloc 函数也⽤来动态内存分配。原型如下:

calloc - C++ Reference (cplusplus.com)

cpp 复制代码
void* calloc (size_t num, size_t size);

从这里我们看出:

• 这个函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。

• 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

举个例子:

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

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

	//calloc函数与malloc一样都是用于申请内存空间的,不同之处在于,calloc会对申请的空间进行初始化
	//申请的空间会被全部初始化为0,其余地方的使用与malloc一样
	if (p == NULL)
	{
		perror("calloc:");
		return 1;
	}

	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}

	free(p);
	p = NULL;
	return 0;
}

运行结果:

4.realloc(内存扩容)

realloc函数的出现让动态内存管理更加灵活。

有时我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整。realloc 函数就可以做到对动态开辟内存大小的调整。

函数原型如下:

realloc - C++ Reference (cplusplus.com)

cpp 复制代码
void* realloc (void* ptr, size_t size);

• ptr 是要调整的内存空间的起始地址

• size 调整之后新⼤⼩

• 返回值为调整之后的内存空间的起始位置。

• 这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到新的空间。realloc函数在调整空间时存在两种情况:

  • 情况一:原有空间后有足够大的空间
  • 情况二:原有空间后没有足够大的空间

情况1

当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化。

情况2

当是情况2 的时候,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩的连续空间来使⽤。这样函数返回的是⼀个新的内存地址。

由于上述的两种情况,因此我们在使用realloc函数时需要多加注意。

举个例子:

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc:");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//空间不够,希望能放20个元素,考虑扩容
	int* ptr = (int*)realloc(p, 80);//realloc函数为内存扩容函数
	//因为realloc也会扩容失败,当扩容失败时,realloc会返回一个空指针
	//如果直接将realloc的返回值给之前开辟空间时的p,那么当返回值为NULL,即扩容失败时,
	//这时候p被赋值为空指针,原来那片开辟的空间就会丢失,造成内存泄漏
	if (ptr != NULL)
	{
		p = ptr;
	}
	//扩容成功,开始使用
 
 
	//不再使用,就释放
	free(p);
	p = NULL;
	return 0;
}

5. 常⻅的动态内存的错误

5.1 对NULL指针的解引⽤操作

举个例子:

大家看看下面这个代码,大家认为这个程序有没有问题?

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

int main()
{
	int* p = (int*)malloc(INT_MAX);
	*p = 20;
	free(p);
}

当我们调试发现,程序会崩溃。

原因是这样的:

首先,使用 malloc(INT_MAX) 来分配内存是一种不太常见且不太安全的做法。INT_MAX 通常是一个非常大的值,可能会超出系统的可用内存,导致分配失败。

在执行 *p = 20; 这一行时,如果 p 的值是 NULL (即内存分配失败),那么这将导致未定义的行为,可能会引发程序崩溃、数据损坏或其他难以预测的错误。

例如,如果系统内存不足,malloc 可能无法成功分配这么大的内存空间,此时 p 就会是 NULL 。当尝试对 NULL 指针进行解引用并赋值时,就像上述代码中那样,很可能导致程序异常终止。

另外,即使内存分配成功,在使用完后通过 free(p); 释放了分配的内存,以避免内存泄漏。但要确保在释放之前对内存的操作都是合法和正确的,避免因错误的操作导致程序出现问题。

总之,在实际编程中,应根据实际需求合理地分配内存大小,并妥善处理内存分配可能失败的情况。

5.2 对动态开辟空间的越界访问

cpp 复制代码
void test()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p != NULL)
	{
		for (int i = 0; i <= 10; i++)
		{
			*(p + i) = i + 1;  //当i是10的时候越界访问
		}
	}
	free(p);
	p = NULL;
}

这段代码存在问题。

在循环中:

cpp 复制代码
for (int i = 0; i <= 10; i++)
{
	*(p + i) = i + 1;
}

*由于分配的内存大小为 10 * sizeof(int) ,所以有效的索引范围应该是 0 到 9 。当 i 等于 10 时,对 (p + 10) 进行赋值会导致越界访问。

5.3 对非动态开辟内存使用free释放

cpp 复制代码
void test()
{
    int a = 10;
    int *p = &a;
    free(p);//ok?
}

在这段代码中,free(p) 是不正确的操作。

p 指向的是一个普通的局部变量 a ,而不是通过 malloccallocrealloc 等动态内存分配函数分配的内存空间。

对非动态分配的内存使用 free 会导致未定义的行为,可能会使程序崩溃或产生不可预测的结果。

例如,程序可能会在执行这一行时突然终止,或者后续的代码执行出现异常。

正确的做法是只对通过动态内存分配获取的指针使用 free 进行内存释放。

5.4 使⽤free释放⼀块动态开辟内存的⼀部分

cpp 复制代码
int main()
{
	int* p = (int*)malloc(100);  // 申请100个字节大小的空间
	if (p == NULL)
	{
		return 1;
	}
	for (int i = 0; i < 10; i++)
	{
		*p = i + 1;
		p++;  
	}
	free(p);
	p = NULL;
	return 0;
}

这段代码存在错误。

在循环中使用 p++ 会导致 p 不再指向最初动态分配的内存的起始位置。当执行 free(p) 时,由于 p 已经不再指向正确的起始位置,这将导致未定义的行为,可能会引发程序错误甚至崩溃。

5.5 对同⼀块动态内存多次释放

cpp 复制代码
void test()
{
    int *p = (int *)malloc(100);
    free(p);
    free(p);
}

这段代码存在错误。

test 函数中,对已经释放的内存指针 p 再次调用 free 函数,这是不合法的操作。

当第一次调用 free(p) 时,所分配的内存已经被归还给系统,p 所指向的内存不再有效。再次调用 free(p) 会导致未定义的行为,可能会使程序崩溃或产生不可预期的结果。

例如,可能会导致内存管理的混乱,影响到其他正在使用的内存区域,或者在某些情况下导致程序直接异常终止。

为了避免这种错误,应该确保对同一个动态分配的内存指针只调用一次 free 函数。

如下面这个代码:

cpp 复制代码
void test()
{
    int *p = (int *)malloc(100);
    free(p);
    free(p);
}

这段代码存在错误。

test 函数中,对已经释放的内存指针 p 再次调用 free 函数,这是不合法的操作。

当第一次调用 free(p) 时,所分配的内存已经被归还给系统,p 所指向的内存不再有效。再次调用 free(p) 会导致未定义的行为,可能会使程序崩溃或产生不可预期的结果。

例如,可能会导致内存管理的混乱,影响到其他正在使用的内存区域,或者在某些情况下导致程序直接异常终止。

为了避免这种错误,应该确保对同一个动态分配的内存指针只调用一次 free 函数。

5.6 动态开辟内存忘记释放(内存泄漏)

cpp 复制代码
void test()
{
    int *p = (int *)malloc(100);
    if(NULL != p)
    {
        *p = 20;
    }
} 

int main()
{
    test();
    while(1);
}

这段代码存在潜在的问题。

test 函数中,虽然分配了 100 个字节的内存,但并没有指定这 100 个字节如何使用。

直接使用 *p = 20; 只是给 p 所指向的内存的第一个 int 大小的位置赋值为 20 。

如果后续的代码期望按照特定的方式使用这 100 个字节,例如将其当作一个包含多个 int 元素的数组,那么这种简单的赋值可能无法满足需求。

此外,如果在后续的代码中不再对这块内存进行其他操作,并且在程序结束时也没有使用 free(p) 释放内存,就会导致内存泄漏。

++⚠️ 忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。++

++切记:动态开辟的空间⼀定要释放,并且正确释放。++

6. 动态内存经典笔试题分析

6.1 题⽬1:

cpp 复制代码
void GetMemory(char* p)
{
	p = (char*)malloc(100);
} 

void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

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

请问运⾏Test 函数会有什么样的结果?

我们运行结果发现,最终程序崩溃了

我们分析一下这个代码究竟出现了什么问题了

  1. **内存非法访问:**我们知道传值调用时,形参只是实参的临时拷贝,对形参的改变无法影响实参,这时str仍是空指针,而strcpy拷贝会对空指针进行解引用操作,对NULL指针解引用会出错!

2. 内存泄漏: 在GetMemory()函数内部动态申请了100字节的空间,因为p随着函数结束而被销毁,所以已经再也找不到该空间,会造成内存泄漏。

改正方法:

  1. 我们要想改变str就需要传址调用,而str本身就是个指针变量,传指针变量的地址需要二级指针来接收
  2. 使用完之后必须释放内存。
cpp 复制代码
void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}

void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	// 释放
	free(str);
	str = NULL;
}

6.2 题目二

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

char* GetMemory()
{
	char p[] = "hello world";
	return p;
}

void test()
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

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

大家可以思考一下,上述代码有没有问题?

运行结果,发现打印的是这样乱码的结果

同学们会感到很疑惑,奇怪,为什么是这样的结果呢?

别急,我们来分析一下:

针对上述问题,我们可以对代码进行如下修改:

① 返回 p ,让 str 接收:

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

// 修改返回类型为char*
char* GetMemory(char *p)
{
    p = (char*)malloc(100);
    return p; // 将p带回来
}

void Test()
{
    char *str = NULL;
    str = GetMemory(str); // 用str接收,此时str指向刚才开辟的空间
    strcpy(str, "hello world"); // 此时copy就没有问题了
    printf(str);
    // 用完之后记得free,就可以解决内存泄露问题
    free(str);
    str = NULL; // 还要将str置为空指针
}

int main()
{
    Test();

    return 0;
}

② 将值传递改为址传递:

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

//              用char**接收
void GetMemory(char** p)
{
    *p = (char*)malloc(100);
}

void Test()
{
    char* str = NULL;
    GetMemory(&str); // 把str地址传递
    strcpy(str, "hello world");
    printf(str);
    // 记得free释放,就可以避免内存泄露问题
    free(str);
    str = NULL; // 还要将str置为空指针
}

int main()
{
    Test();

    return 0;
}

经过我们的修改,我们运行代码,hello world 就打印出来了

6.3 题⽬3:

cpp 复制代码
void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
} 

void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

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

请问运⾏Test 函数会有什么样的结果?

运行结果,我们发现虽然打印出hello,但是这个代码还是有一点问题的

分析 :Test函数里面将str进行传址调用,在GetMemory函数里面申请100个字节大小的空间,将hello拷贝到str所指向的空间中,但是使用之后并没有使用free函数进行释放,导致内存泄漏。
解决 :申请的空间使用完之后要使用free函数进行释放,并将str置为空指针

6.4 题⽬4:

cpp 复制代码
void Test(void)
{
    char *str = (char *) malloc(100);
    strcpy(str, "hello");
    free(str);
    if(str != NULL)
    {
        strcpy(str, "world");
        printf(str);
    }
}

请问运⾏Test 函数会有什么样的结果?

分析:Test函数里面str申请了100个字节的空间,将hello拷贝到str所指向的空间中,就直接用free释放掉了,导致str成了野指针,之前将hello拷贝到str中,所以str一定不是空指针,

因为 free(str); 之后 str 成为野指针, if(str != NULL) 语句不起作⽤。

6. 柔性数组

同学们可能从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。

C99 中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做**『柔性数组』**成员。

例如:

cpp 复制代码
typedef struct st_type
{
    int i;
    int a[0];//柔性数组成员
}type_a;

有些编译器会报错⽆法编译可以改成:

cpp 复制代码
typedef struct st_type
{
	int i;
	int a[];//柔性数组成员
}type_a;

6.1 柔性数组的特点:

结构中的柔性数组成员前⾯ 必须⾄少⼀个其他成员

sizeof 返回的这种结构大小不包括柔性数组的内存

包含柔性数组成员的结构⽤malloc ()函数进⾏内存的动态分配,

并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。

⚠️ps:除了malloc函数,realloc、calloc等动态内存开辟的函数也需要类似的操作

例如:

cpp 复制代码
typedef struct st_type
{
	int i;
	int a[0];//柔性数组成员
}type_a;

int main()
{
	printf("%d\n", sizeof(type_a));
	return 0;
}

输出结果是4

说明:sizeof 返回的这种结构大小不包括柔性数组的大小

6.2 柔性数组的使⽤

比如说我现在要数组a里面有10个元素,现在进行malloc一下
示例如下:

cpp 复制代码
#include<string.h>
#include<errno.h>
struct st_type
{
	int i;//4字节
	int a[0];//柔性数组成员,也可以写int a[];
};
int main()
{
    //假设我现在需要a里有10个元素
	struct st_type*ps=(struct st_type*)malloc(sizeof(struct st_type) + 10 * sizeof(int));
	if (ps == NULL)//由于空间可能不够开辟导致malloc开辟失败,开辟失败会返回空指针
	{
		printf("%s\n", strerror(errno));
		return -1;//程序出问题后,跳出程序
	}
	//开辟成功
	int j = 0;
	for (j = 0;j < 10;j++)
	{
		ps->a[j] = j;
	}
	for (j = 0;j < 10;j++)
	{
		printf("%d ", ps->a[j]);//打印0-9
	}
	printf("\n");
	//如果想继续用柔性数组a进行打印
	//比如现在a里只有10个元素,我用完10个了,我还要继续来10个,用realloc追加
	struct st_type*ptr=realloc(ps, sizeof(struct st_type) + 20 * sizeof(int));//ps:realloc第二个参数是调整后的整体大小
	if (ptr == NULL)
	{
		printf("扩容失败\n");
		return -1;
	}
	else
	{
		ps = ptr;
	}
	//扩容成功
	int k = 0;
	for (k = 10;k < 20;k++)
	{
		ps->a[k] = k;
	}
	for (j = 0;j < 20;j++)
	{
		printf("%d ", ps->a[j]);//打印0-19
	}
	//释放空间
	free(ps);
	ps = NULL;
	return 0;
}

我们这里需要数组a里有10个元素,那我们malloc的时候要对结构体里的整形i先开辟4个字节,然后为整形数组a再开辟40个字节,然后malloc函数返回开辟空间的起始地址,赋给truct st_type * 类型的ps指针。

malloc(sizeof(struct st_type) + 10 * sizeof(int))这个操作等价于struct st_type类型创建一个变量所占空间,只不过是用malloc来开辟

你改变数组a大小,追加空间时,realloc(ps, sizeof(struct st_type) + 20 * sizeof(int)),realloc的第一个参数仍然是ps,因为你当时是用malloc一次开辟出的一块空间,你是不能单独调整数组a的空间的

6.3 柔性数组的优点

柔性数组就是对一块空间实现动态开辟嘛,那我们之前也讲过指针来动态内存开辟,我们来看一段代码来对比一下这两种方法:

cpp 复制代码
//用指针也可以做到a指向的空间动态变化
struct st_type
{
	int i;//4字节
	int *a;//4字节,这里计算结构体大小恰好是8字节
};
int main()
{
	struct st_type*ps = (struct st_type*)malloc(sizeof(struct st_type));
	ps->i = 100;
	ps->a = (int*)malloc(10 * sizeof(int));//a指向40个字节的空间,该空间由int*进行管理
	int j = 0;
	for (j = 0;j < 10;j++)
	{
		ps->a[j] = j;//a[j]=*(a+j)
	}
	for (j = 0;j < 10;j++)
	{
		printf("%d", ps->a[j]);
	}
	//a指向的空间不够了,希望调整大小
	int *ptr = (int*)realloc(ps->a, 20 * sizeof(int));
	if (ptr == NULL)
	{
		printf("扩容失败");
		return -1;
	}
	else
	{
		ps->a = ptr;
	}
	//使用...
	//释放
	free(ps->a);
	ps->a = NULL;
	free(ps);
	ps = NULL;
}

这里需要注意的是,在释放空间时,你要先释放指针a指向的空间,然后释放结构体指针

如上图,我们结构体指针ps开辟一块空间,空间里存放整形i和整形指针a,a又malloc(后续如果需要还可以realloc追加)一块空间,如果你先释放掉ps,a就没了,你就没法找到a指向的那块空间了。

这里对比柔性数组,柔性数组和上述的指针都可以实现一块空间大小的调整,

但是柔性数组有两个好处:

第一个好处是:方便内存释放

如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。以上,如果我把结构体的内存及其成员要的内存一次性分配好,并返回给用户一个结构体指针,用户做一次free就可以把所有内存都释放掉,并且不用考虑前面说的释放的顺序。
第二个好处是:加快访问速度

连续的内存有益于提高访问速度,也有益于减少内存碎片。

ps:内存碎片如下图

橙色部分表示malloc开辟的空间

操作系统给我们一块内存,我们在进行malloc时,不一定就是一块连着一块的,

上图的空白部分就是内存碎片,有些类似我们在生活裁剪布料时,剪下来的一些剩余的边角料一样

扩展阅读:同学们有兴趣了解柔性数组的更多内容,可以阅读下面这篇文章~

C语言结构体里的成员数组和指针 | 酷 壳 - CoolShell

7. 总结C/C++中程序内存区域划分

C/C++程序,对于内存分配了如下几个区域:

这里我们简单了解一下:

  1. 栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内存容量有限。 栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。

  2. 堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配⽅式类似于链表。

  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。

  4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码

补充阅读:陈浩 大佬在14年发布的一篇关于成员数组的博客 C语言结构体里的成员数组和指针

8. 总结

内存管理是一项非常重要的任务。动态内存管理是指在程序运行时分配和释放内存的过程。通过动态内存管理,我们可以根据需要分配适当的内存空间,并在不再需要时释放它。这使得程序更加灵活,并能够处理各种大小和形状的数据。

以上就是动态内存管理的所有内容了~~~

如果对你的学习有所帮助,别忘了收藏和点赞,有疑问随时可以在评论区骚扰我呦~

相关推荐
湫ccc1 分钟前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe39 分钟前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin41 分钟前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
Ysjt | 深1 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__1 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞1 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货1 小时前
Rust 的简介
开发语言·后端·rust
湫ccc2 小时前
《Python基础》之基本数据类型
开发语言·python