动态内存管理+柔性数组

动态内存存在的意义

C语言是一种过程式编程语言,提供了底层访问能力和丰富的功能,广泛应用于操作系统、嵌入式系统、硬件驱动程序等领域。C语言的动态内存管理主要是通过`malloc()`、`calloc()`、`realloc()`和`free()`这几个标准库函数来实现的。

理解动态内存管理的意义,需要从以下几个方面来看:


1. **数据结构灵活性**:

静态内存分配在编译时就已经确定,这限制了程序在运行时数据结构的可调整性。动态内存管理允许在程序运行时根据需要动态地分配和释放内存,这为实现灵活的数据结构提供了可能,如链表、树、图等。


2. **资源优化**:

动态内存管理使得程序能够根据实际需要分配内存,而不是一开始就分配固定大小的内存。这样可以避免内存的浪费,尤其是在处理不确定大小的数据集或者需要动态扩展的数据结构时。


3. **性能提升**:

通过动态内存管理,程序可以在需要时分配更多的内存资源,这样可以提高程序在处理大量数据时的性能。


4. **避免内存泄漏和溢出**:

动态内存管理要求程序员在使用完内存后及时释放,这样可以避免内存泄漏。同时,动态分配内存时,系统会检查分配请求是否合法,避免因超出程序所能管理的内存范围而导致的内存溢出。


5. **实现复杂算法和数据结构**:

一些复杂的算法和数据结构,如斐波那契堆、红黑树等,它们的实现依赖于动态内存管理来维持其内部状态和数据关系。


6. **跨平台兼容性**:

动态内存管理提供的内存分配和释放机制,保证了在不同平台和操作系统上的一致性,便于程序的移植。


然而,动态内存管理也带来了额外的负担,程序员需要手动管理分配和释放的内存,否则容易产生内存泄漏和野指针等问题。因此,在使用动态内存时,需要格外小心和谨慎,确保每一个动态分配的内存块在使用完毕后都能被正确释放。

简单的说就可以自己申请动检使用

自己申请动态内存空间

目的是灵活的申请控制空间

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

什么是动态内存

动态内存是指在程序运行过程中可以被分配和释放的内存空间。这与静态内存分配相对,静态内存分配是在程序编译时就已经确定的内存空间,其大小在程序运行期间固定不变。
在许多编程语言中,特别是在C语言中,动态内存管理是通过一系列标准库函数来实现的,如`malloc()`、`calloc()`、`realloc()`和`free()`。这些函数提供了在程序运行时请求和释放内存的能力。
动态内存的分配通常发生在堆(Heap)区域,这是程序内存空间的一个部分,用于存储动态分配的内存。与之相对的还有栈(Stack),栈是用于存储函数调用信息和局部变量的内存区域,其分配和释放是自动的,通常在函数调用开始和结束时进行。
动态内存管理的意义在于它提供了灵活性,允许程序在运行时根据需要分配和释放内存,这对于实现复杂的数据结构和算法、优化资源使用、以及处理不确定大小的数据集等方面至关重要。然而,同时它也要求程序员必须谨慎使用,避免内存泄漏和野指针等问题。

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 malloc(申请函数)free(释放函数)

 malloc和free的语法格式

`malloc` 函数是 C 语言标准库中的一个重要函数,用于动态分配内存。其语法如下:


void *malloc(size_t size);


这里的 `void *` 表示返回的是一个 `void` 类型的指针,实际上这个指针指向的是一个 `char` 类型的内存块。`size_t` 是一个无符号整数类型,用于指定分配的内存大小。
当你调用 `malloc` 函数时,你需要提供一个参数 `size`,表示你需要分配的内存的大小(以字节为单位)。函数返回一个指针,指向为你分配的内存区域的开始位置。如果分配成功,这个指针不会是 `NULL`;如果分配失败(比如,系统没有足够的内存来满足请求),`malloc` 函数会返回 `NULL`。
在使用 `malloc` 分配的内存时,你需要使用 `memcpy`,`strcpy` 等函数来复制数据到这个内存区域,而不是 `strcpy`,`printf` 等函数,因为这些函数不适用于动态分配的内存。
在使用完动态分配的内存后,你应该使用 `free` 函数来释放内存,以避免内存泄漏。


free(void *ptr);


这里的 `ptr` 是之前 `malloc` 或其他动态内存分配函数(如 `calloc`,`realloc`)返回的指针。使用 `free` 后,指针所指向的内存将被释放,可以被系统重新分配给其他请求。但是,免费的内存不会立即变得可被其他程序使用,这是一个操作系统和硬件相关的过程。

这里是需要把这两个函数放到一起讲解的,因为申请内存空间你就得释放内存空间,不然会导致内存泄漏,内存泄漏会导致电脑内存里面可以使用的空间越来越少。最后导致崩盘。

———————————————————————————————————————————

malloc和free函数的使用 

申请内存空间

返回函数 一定要做检查

头文件stdlib.h

perror打印出错误

动态内存的开辟空间都在堆区


释放内存

传递的参数 p就可以

解释一下

所以 此时释放的是自己的权限

但是需要知道的是 此时p也就是野指针了

所以此时我们把p栓到树上

也就是设置为空指针

———————————————————————————————————————————

 这里开辟内存空间注意的三点(重点)

1

这里需要进行强制类型的转化因为从语法格式我们可以看出来,这里是的void*类型,也就是不明确的类型,为什么,因为C语言官方在设定的时候不清楚你开辟内存空间的目的是干什么用的,不知道是用于整数的存储,还是用于字符的打印,还是干什么。所以这里给出无返回类型,只需要你在使用的时候进行强制类型转化就可以。这样可以提升代码的兼容性和健壮性。

2.

对于空间的释放,释放空间之后尽量的让指针指向NULL,也就是空指针,因为当指针指向的空阿金释放之后,本质上他是不指向任何的数值了,也就是此时他是野指针了,虽然你不使用他了,但是最好指向空指针。防止内存的泄露

3.

使用空间的时候,你不能让指针跟着走,我们可以看到代码里面,

使用的是这个代码*(p+i)=I+1;。而不是*(p)=i;p++;这样的代码。

为什么,因为当指针走远之后,你进行释放空间的时候,本质上释放的是指针移动后指向的空间,你让指针一直移动,移动到开辟的空间的最后一个位置,然后再进行释放空间的时候,其实本质上释放的是开辟空间之后的空间,也就是没有释放空间。

你创建了空间,但是没有释放空间,此时会导致内存泄露。

内存泄露的问题我们也聊过,也就是内存占用少的时候还不明显,但是当有大量程序运行的时候,就会导致内存一直占用,但是得不到释放。从而导致崩盘。

4.

malloc函数里面放置的是字节大小,这里放置的是字节大小,不是bit大小,这里一定要记住。

———————————————————————————————————————————

malloc和free代码

int main()
{
	//malloc不初始化开辟空间
    //开辟20个字节的空间大小 
	int* p = (int*)malloc(5 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc:");//这里是打印错误信息,
		return 1;
	}
	for (int i = 0; i < 5; i++)//这里是打印出来开辟的空间 赋值之后进行打印 
	{
		*(p + i) = i + 1;//这里是进行赋值 赋值从1开始
		printf("%d ", *(p + i));//这里是打印出来
	}
	free(p);//这里进行指针的释放
	p = NULL;//防止野指针的问题,我们指向空指针
	return 0;
}

malloc第两种写法

可以是sizeof(int)=四个字节

然后5*sizeof(int)=20个字节

第二种就是上述的写法malloc(20)

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 calloc(申请空间初始化

calloc(申请空间初始化)

语法格式

在C语言中,`calloc` 和 `realloc` 是用于动态内存分配的两个函数。它们都定义在头文件 `<stdlib.h>` 中。

1. `calloc`

函数用于在内存的动态存储区分配空间,并初始化每个字节为 0。其函数原型如下:


void *calloc(size_t num, size_t size);

- `num`: 分配块的数量。
- `size`: 每个块的大小(以字节为单位)。
`calloc` 返回一个指向分配内存的指针。如果分配失败,则返回 `NULL`。

———————————————————————————————————————————

calloc函数的使用

 我们直接看是不直观的,我们可以这样理解

malloc(5*sizeof(int))=calloc(5,sizeof(int))

这两个是等价的 

calloc函数其实和malloc函数本质都是一样的唯一不一样就是,calloc函数在开辟空间的时候会进行初始化,当然 calloc函数初始化,那运行速度也会比malloc慢一点。malloc函数初始化, 会快一点

其他的没区别

——————————————————————————————————————————— 

calloc和free代码

int main()
{
	//malloc不初始化开辟空间
    //开辟20个字节的空间大小 
	int* p = (int*)calloc(5 , sizeof(int));
	if (p == NULL)
	{
		perror("malloc:");//这里是打印错误信息,
		return 1;
	}
	for (int i = 0; i < 5; i++)//这里是打印出来开辟的空间 赋值之后进行打印 
	{
		//*(p + i) = i + 1;//这里是进行赋值 赋值从1开始
		printf("%d ", *(p + i));//这里是打印出来
	}
	free(p);//这里进行指针的释放
	p = NULL;//防止野指针的问题,我们指向空指针
	return 0;
}

这里我们可以看到和malloc一模一样,只是换了一下函数,其他是没有变化的。 

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

realloc(动态内存二次的大小调整,也就是调整空间大小)

语法格式

1. `realloc`

函数用于动态地改变之前分配的内存块的大小。其函数原型如下:


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

- `ptr`: 指向之前分配的内存块的指针。如果 `ptr` 为 `NULL`,那么 `realloc` 行为与 `malloc` 相同。
- `size`: 新分配的大小(以字节为单位)。
`realloc` 可以调整内存块的大小,如果新的大小比原来小,那么内存块可能会被移动。如果新的大小比原来大,那么内存块会尝试扩展。如果扩展失败,那么原来的内存块会被保留,只是其大小变为新的大小。`realloc` 返回一个指向调整大小后的内存块的指针。如果调整大小失败,则返回 `NULL`。
在使用这些函数时,应当总是检查返回的指针是否为 `NULL`,以避免潜在的空指针引用错误。并且在使用 `calloc` 或 `realloc` 分配的内存后,应当在不再需要时使用 `free` 函数来释放内存,以避免内存泄露。

———————————————————————————————————————————

realloc函数的使用 

这个函数的代码格式可以理解为 ,你之前malloc或者calloc申请的空间是p指针进行接收的,OK此时你想对这个空间进行空间的扩展,那此时的ptr也就是p指针变量,size也就是你需要的实际空间,这里的空间不是扩展的空间,而是实际的空间
void *realloc(void *ptr, size_t size);

也就是realloc(p,40)

之前我们p开辟的是20的空间 ,这里我们实际需要的是40的空间,这不是再开辟40空间,而是一共40 的空间。

下面解释一下为什么

———————————————————————————————————————————

realloc函数扩展空间的原理

 realloc函数扩展空间几乎就是两种模式

1. 直接在原内存空间的基础上进行扩展内存

2. 把原空间的内存复制到新的内存空间,建立一个更大的内存空间,需要知道的是,只要是进行内存的复制,那么就有可能导致复制失败,从而导致数据丢失。

所以实际使用的时候,我们还是需要进行判断语句的。

 如图1

如图2 

———————————————————————————————————————————

realloc代码解析 

首先我们创建一个空间 ,然后进行调整 

因为这里使用的时候 是有可能导致开辟空间错误,导致地址的丢失,所以我们进行判断,也就是主动的找一个指针变量指向新开辟的地址,只要指向的空间不是空指针,那么也就是可以满足条件,然后进行一个二次赋值

最后,不使用的时候进行申请空间的清理,防止内存空间的泄露

———————————————————————————————————————————

realloc代码 

int main()
{
	//malloc不初始化开辟空间
    //开辟20个字节的空间大小 
	int* p = (int*)calloc(5 , sizeof(int));
	if (p == NULL)
	{
		perror("malloc:");//这里是打印错误信息,
		return 1;
	}
	for (int i = 0; i < 5; i++)//这里是打印出来开辟的空间 赋值之后进行打印 
	{
		//*(p + i) = i + 1;//这里是进行赋值 赋值从1开始
		printf("%d ", *(p + i));//这里是打印出来
	}
	free(p);//这里进行指针的释放
	p = NULL;//防止野指针的问题,我们指向空指针



	//首先我们已经开辟动态内存空间 但是不能满足我们的需求,那么此时我们就用realloc进行空间的扩展
	//依旧是采取强制类型转化,然后我们这里是延伸到40个字节的空间
	//因为我们知道,开辟空间的时候可能会导致内存空间的扩展失败
	//所以我们不能直接p=realloc(int*)realloc(p, 10 * sizeof(int)); 
	//而是进行先搞一个新的指针变量,确定成功了,再进行赋值
	//也就是
	int* ptr = (int*)realloc(p, 10 * sizeof(int));
	if (ptr == NULL)
	{
		perror("realloc:");//打印错误信息如果是空指针
		return 1;
	}
	p = ptr;
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = 0;
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

这里是15个0 

因为calloc也打印5个0 

不要误解

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

malloc+free+calloc+realloc函数的综合使用

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
	//malloc不初始化开辟空间
	//开辟20个字节的空间大小 
	int* p = (int*)malloc(20);
	if (p==NULL)
	{
		perror("malloc:"); 
		return 1;
	}
	//使用这20个字节的空间
	printf("malloc开辟空间的使用:");
	for (int i = 0; i < 5; i++)
	{
		*(p + i) = i + 1;
		printf("%d ", *(p + i));
	}
	//释放开辟的空间
	free(p);
	//释放空间后不让p是野指针 指向空指针
	p = NULL;
	printf("\n\n\n");



	//calloc开辟初始化空间 
	//这里是开辟5个sizeof 整形大小的空间 也就是20个字节大小的空间 但是 这个空间的连续的
	int* s = (int*)calloc(5, sizeof(int));
	if (s == NULL)
	{
		perror("calloc:");
		return 1;
	}
	printf("calloc开辟空间的使用:");
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", *(s + i));
	}
	//释放开辟的空间
	free(s);
	//释放空间后不让s是野指针 指向空指针
	s = NULL;
	printf("\n\n\n");



	//realloc调整空间大小
	//这里是开辟5个sizeof 整形大小的空间 也就是20个字节大小的空间 但是 这个空间的连续的
	int* a = (int*)calloc(5, sizeof(int));
	if (a == NULL)
	{
		perror("calloc:");
		return 1;
	}
	printf("realloc的使用\ncalloc开辟空间的使用:");
	for (int i = 0; i < 5; i++)
	{
		*(a + i) = i + 1;
		printf("%d ", *(a + i));
	}
	printf("\n");
	//调整空间大小,但是需要知道的是 空间调整大小的时候,容易导致丢失 所以我们首先检验一下 再进行赋值
	int* ptr = (int*)realloc(a, 40);
	if (ptr == NULL)
	{
		perror("realloc:"); 
		return 1;
	}
	a = ptr;
	//这里虽然是对于开辟空间的使用 但是实际上也是对于空间的覆盖
	for (int i = 0; i < 10; i++)
	{
		*(a + i) = i + 5;
		printf("%d ", *(a + i));
	}
	free(a);
	a = NULL;

	return 0;
}

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

空指针的解应用操作(错误信息合集)

越界访问

首先我们上一个代码,看看这个的代码的问题 

这个代码的问题显而易见 ,就是在循环里面,产生了越界访问的问题,这里你开辟了10个整形空间,但是从0-10一共是11个整形空间。导致访问不合法的空间,从而导致内存泄露,内存泄露会导致,这个内存一直使用,逐步积累,从而导致内存呗占满。 

——————————————————————————————————————————

free只能释放动态的空间

上面的重点我们说的很清楚,为什么不能让指针移动 

这里我们写上一个代码

指针移动之后 你指的是静态的空间,我们的是什么,我们的函数的动态内存空间的管理。

这里释放的是 p之后的空间

这里 的p不再是动态申请的起始位置

指针跑远了 也就是不指向释放空间了

所以才需要*(p+i)=i+1;

———————————————————————————————————————————

重复释放

也就是两个free

也就不行的

但是你要是 释放之后指向为空指针 那就没有什么问题

重复释放会导致的问题:
1. **不可预知的行为**:

当你重复释放同一块内存时,那个内存区域可能已经被重新分配给了其他用途。如果你再次试图访问它,你可能会读取到未定义的数据,或者更糟糕的是,破坏了其他部分的程序。
2. **内存冲突**:

如果你在释放内存之后又重新分配了相同的大小,并且期望得到相同的内存地址,这是不确定的。每次调用`malloc`或`realloc`都可能返回一个新的内存地址。因此,即使你的代码在逻辑上看起来可以工作,这也是依赖于实现的,并且不应该依赖这种行为。
3. **内存泄漏**:

虽然重复释放内存不会直接导致内存泄漏(因为内存已经被释放),但这通常意味着管理内存不当,可能会导致其他内存泄漏问题。
4. **性能问题**:

在某些情况下,操作系统和内存管理器可能会对重复释放小块内存的行为进行优化,如通过释放和立即重新分配来减少碎片。然而,这取决于具体的实现和配置,不应该依赖于此。
为了避免这些问题,你应该确保每个`malloc`或`realloc`调用都有一个对应的`free`调用,并且只释放一次。一个常用的做法是,在每次调用`malloc`或`realloc`时,都记录下返回的指针,然后在`free`中检查是否是同一个指针,避免重复释放。

———————————————————————————————————————————

忘记释放

导致内存泄露的问题

内存是一种资源 当申请之后不用的时候是需要回收的

其实malloc calloc realloc 申请的内促 如果不想使用 可以使用free释放

如果没有使用free来释放当程序运行结束的时候 也会由操作系统回收的

也就是 一直不退出的话 那个内存别人也用不上

所以 如果申请不释放的话 必须要进行说明

1.谁申请是谁释放------------------malloc free成对出现 --不然就会导致内存泄露的问题 --即使成对出现 也会导致泄露问题

就比如,这里也就是出问题了 即使malloc和free成对出现 也是出现问题了

2.如果不释放 也就是需要告诉使用的人,记得释放

———————————————————————————————————————————

传值调用错题详解(重点)

首先我们来看这个错误代码 

 

首先我们看代码逻辑,首先main函数调用test,test接收的是void类型,设置一个指针变量,指向null,传递给get函数,也就是传递一个空指针给getmemory函数,这个函数接收了,然后进行运算。

接下来我们进行分析代码的错误逻辑。

为什么不取地址是错误的呢,

  1. 传递地址(指针的指针): 当你传递 &strstr 的地址)给函数时,你是在告诉函数你想要操作的是 str 变量的内存地址本身。这允许函数直接修改 str 变量的值,或者使用 str 变量的地址进行其他操作。
  2. 传递指针(不取地址): 当你传递 str(指向 char 的指针)给函数时,你是在告诉函数你想要操作的是 str 指向的字符串。函数可以通过 str 指针访问和修改字符串的内容,但不会直接修改 str 指针本身的值。

这样会导致什么情况呢,就是内存销毁的问题,你这个函数传递的时候,传递的是指针,不是指针的地址。那么此时就会产生一种情况那就是,getmemory函数运算结束,这个函数进行了销毁。

因为他调用结束之后,函数会进行销毁,strcpy函数想拷贝到开辟的空间里面,OK!找不到空间了,因为进行了销毁。

什么意思呢,可以理解为,

张三,有一天忙碌完,不想回家了,在公司楼下开了个酒店住进去了,哎,很不错,告诉李四,我住的209房间特别好,你有空可以去试试。第二天李四也累了,那我也去了,我到地方我就记得209房间,但是张三的209房间已经退房了,那我肯定进不去,我非得进去,那就被保安打一顿。

你这里也是这样,你这里 想拷贝字符创,那可以没问题。关键点在于你这里传递的是数值,他不是地址,那数值的话,函数创建了,然后运行结束进行销毁。你自然找不到房间号。你这里要是传递的是空间的地址,那我可以直接去你的地址进行操作拷贝,你也不需要进行返回什么的,哪怕进行销毁了,那也没事。

1.因为这里传参传递的是数值,函数返回的时候进行了函数的销毁,从而导致非法访问

这里的意思 应该是把100个字节放到str里面去 但是传值调用是不行的 会导致有问题

正确的处理应该是什么呢

我们可以把str取地址,传参传的是地址,然后因为本身str已经是指针,我们采取用二级指针的方式进行接收,也就是指向的是指针的地址,

修改为正确代码1

此时就可以完成任务了 

修改为正确代码2

当然 不喜欢二级指针 也可以进行数值的接受 也就是char*

找个数值进行接收,找个数值进行接收,那就是有返回类型,我们采取的返回类型是char*类型,因为这里是拷贝字符串。接收返回值也是一样的。

提示一下,所以其实不传参也是可以的

图解 

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

动态管理内存题目详解

举例(传值调用问题)

动态内存管理-传值调用错题解析-CSDN博客

这里给一个代码进行分析

这里的错误很明显,在上面的传值调用问题详解里面,我们说的很清楚,这里我们重新复述一下 ,上面的链接是传址调用链接,我专门列出博客进行说明。可以反复观看

这个代码可能产生的问题是 有可能打印出来 也有可能打印不出来

为什么呢,我们看看代码的逻辑

首先调用函数,创建str,str=null,get调用,数组里面 放一个hello word,这个数组就产生了,此时返回数组,但是这里返回的是p,也就是数组首元素地址。这里返回首元素的地址,但是会产生的的问题就是,函数创建之后会进行销毁,你返回的是首元素的地址,这里空间进行了销毁,这个函数去找的时候,找不到这个地址。

下面进行图解:

假设这个地址 是这个

也就是 当传递数组 这里的空间已经释放

也就是返回的是空指针

也就是 这里是非法访问

当然 如果 空间 没有呗占用的时候 还能打印出来

但是不管有没有在占用 此时也都是非法访问

也就是

函数 这里是创建之后 返回之后 会进行销毁的

也就是销毁之后 这个函数 这个数组销毁了 也就是 有可能被覆盖 也可能不进行覆盖

也就是 这样的情况下 会出现野指针的情况

这里的情况也就是返回栈空间地址的问题

返回就是野指针

——————————————————————————————————————————— 

举例(返回变量问题)

这里也是返回栈空间地址的问题

我们进行逻辑代码的分析

首先我们进入main函数,进入函数里面调用test,用指针p进行接收,然后我们进入test函数,n赋值等于10,此时返回数值。

这里错误就错在返回的是地址,之前我们说到过,地址是会进行销毁的,函数创建之后,返回的时候进行销毁,还是那个找酒店的例子。我们是找不到的。

但是 返回局部变量是可以的

但是 返回局部空间的地址是不可以的 因为地址会进行销毁的

这两句话什么意思呢,就是返回一个数值是可以的,比如返回n,但是返回n的地址是不行的,n是局部变量,局部变量在函数销毁的时候是一并销毁的。

———————————————————————————————————————————

举例(忘记free问题)

这里没有任何问题 但是 唯一的问题 就是没有

free

需要 free一下 指向空指针一下

即使这个程序没有free但是 会自动free

但是 万一程序不退出

那就是大问题

也就是 空间溢出问题,程序一直占用,得不到释放,从而导致内存占用满了,从而导致崩盘。

解决办法就是只需要在最后加上free(str);进行内存空间的释放 

———————————————————————————————————————————

举例(忘记置为空指针问题)

这个代码的问题是, 提前释放

也就是这里拷贝之后就还给操作系统,我们还没有判断和打印,就已经进行销毁创建的空间了,导致的错误

也就是我们没有使用权限了

但是这一块空间 还在

也就是开酒店 虽然你退房了

但是房间还在

但是 虽然我这个空间 还在 但是 我没有使用空间

实际修改代码

也就是 free使用之后,置为空指针 这样后面也就没有什么意义了,这里才是考察的知识点

你也可以修改为这个free放到最后进行释放,这个也是没有问题的 ,但是这个不是我们的考察点。

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

柔性数组

柔性数组存在的意义

 柔性数组在编程语言中指的是可以动态调整大小的数组。相比固定大小的数组,它们提供更大的灵活性,主要存在以下意义:


1. **动态调整大小**:

在程序运行过程中,如果需要增加或减少数组的大小,柔性数组可以避免创建新的数组和复制元素到新数组中的开销。


2. **节省内存**:

柔性数组在初始化时不需要指定确切的大小,因此可以在不知道确切需求的情况下启动程序,节省了初始化的时间和内存空间。


3. **提高效率**:

由于不需要预先分配固定大小的内存,柔性数组可以在数组实际需要的大小上分配内存,减少了内存的浪费。


4. **易于扩展**:

当程序需求变化,需要增加存储空间时,柔性数组更容易进行扩展。


5. **减少代码复杂性**:

通过动态内存管理,程序员可以简化代码,不必担心数组大小的问题。


6. **灵活的数据处理**:

在处理数据时,如果数据量不稳定或者在处理过程中数据量发生变化,柔性数组可以更有效地处理这些情况。
在不同的编程语言中,如C++中的`vector`,Java中的动态数组,都是柔性数组的实现。这些数据结构在现代软件开发中非常常见,因为它们提供了一种高效、灵活的方式来处理动态变化的数据集合。

———————————————————————————————————————————

柔性数组建立在结构体里面

而且是最后一个成员(这里一定要记住,是最后一个成员)

数组没有指定大小

这个数组才是柔性数组

至少有一个其他成员 (柔性数组的条件)-->判断条件

——————————————————————————————————————————— 

柔性数组写法1

—————————————————————————————————————————— 

柔性数组写法2

至少有一个其他成员 (柔性数组的条件)

———————————————————————————————————————————

柔性数组的如何在计算机里面计算的 

内存---这里只计算n的空间,不计算柔性数组arr的空间,

不计算 柔性数组的空间,这里通过计算机的计算,我们发现确实是这样

———————————————————————————————————————————

柔性数组开辟空间的使用逻辑(重点)

首先给出一个代码 我们详细解释代码的逻辑

那么柔性数组在这里面是如何使用的呢

1.首先我们进入主函数这里开辟一个空间,空间的大小是一个结构体大小的空间,这里是计算的大小是不包括弹性数组的大小的,计算的是弹性数组之前的大小,因为弹性数组在正式使用之前是没有大小空间这一说的。

malloc(sizeof(struct s));

2.我们开辟完空间之后,如果是按照正常的开辟空间来说,那么此时开辟的空间只需要进行强制类型转化就可以结束了。但是我们这里讲解的是柔性数组,也就是还没有结束。

malloc(sizeof(struct s)+5*sizeof(int));

3.柔性数组的开辟空间,也就是,结构体空间(不包含弹性数组的大小的空间)+你希望的空间大小。因为你的结尾处是有弹性数组的,弹性数组是可以任意变化的。所以后面的加上,加上的是可以变化的空间,也就是弹性数组的空间 ,当然,你要是不加上空间也是可以的。最后进行强制类型转化,并且创建指向这个空间的变量。

struct s*ps=(struct s*)malloc(sizeof(struct s)+5*sizeof(int));

也就是这样用指针进行接收,强制类型转化为结构体,因为是结构体类型的,自然需要用结构体进行接收,结构体进行强制类型转换。

这段代码的图解

这段代码意味着

四个字节给n

20个字节给arr

把这个地址给ps

这里ps指向的是ps这一框空间

这里也可以通过realloc调整空间

柔性数组为什么放到后面:

也就是 结构体的这个大小是柔性变化的

所以需要放到后面 方便空间的柔性

使用

柔性数组

———————————————————————————————————————————

柔性数组延长开辟空间的使用逻辑(重点)

这里神奇的来了 realloc函数可以二次对空间进行扩展。

这里可以进行扩展空间

1,malloc在弹性数组里面的使用(上面讲解复制来的)

1.首先我们进入主函数这里开辟一个空间,空间的大小是一个结构体大小的空间,这里是计算的大小是不包括弹性数组的大小的,计算的是弹性数组之前的大小,因为弹性数组在正式使用之前是没有大小空间这一说的。

malloc(sizeof(struct s));

2.我们开辟完空间之后,如果是按照正常的开辟空间来说,那么此时开辟的空间只需要进行强制类型转化就可以结束了。但是我们这里讲解的是柔性数组,也就是还没有结束。

malloc(sizeof(struct s)+5*sizeof(int));

3.柔性数组的开辟空间,也就是,结构体空间(不包含弹性数组的大小的空间)+你希望的空间大小。因为你的结尾处是有弹性数组的,弹性数组是可以任意变化的。所以后面的加上,加上的是可以变化的空间,也就是弹性数组的空间 ,当然,你要是不加上空间也是可以的。最后进行强制类型转化,并且创建指向这个空间的变量。

struct s*ps=(struct s*)malloc(sizeof(struct s)+5*sizeof(int));

也就是这样用指针进行接收,强制类型转化为结构体,因为是结构体类型的,自然需要用结构体进行接收,结构体进行强制类型转换。

2,realloc在弹性数组里面的使用讲解:

1.首先我们知道realloc函数的使用规则,realloc(目标函数,扩展的空间的大小),同时realloc使用的时候可能会导致开辟空间的失败,所以我们需要先拿指针进行接收,再进行判断和赋值。

2.我们需要知晓弹性数组的使用规则,也就是sizeof计算的是字节的大小是不包含弹性数组的。所以需要加上你需要的实际的空间的大小。也就是,原本20个字节的大小,你扩展40,那么加一起就是60空间的大小。你可以不使用,但是使用的时候,最多可以使用的是60个空间的大小。

3.接下来我们进行代码的解释,malloc的代码逻辑是一样的,所以我们从realloc开始讲解,这里的我们直接使用realloc是可能开辟空间导致失败的,所以我们选择用一个指针指向开辟的空间,只要不是空地址,我们就继续下面的工作。也就是

int *ptr=(struct s*)realloc(ps,sizeof(struct s)+10*sizeof(int));

解释:这里开辟的是ps指向的空间,创建指针变量指向ps指向的空间,防止创建的时候失败,导致销毁。

4.总结使用代码,想要进行赋值,打印出来,最后我给出了代码。

柔性数组第一种的代码总结

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
struct s
{
	int i;
	int arr[];
};
//sizeof(struct s):这个表达式计算了 struct s 类型的大小。
// 这包括了 struct s 中所有字段的大小,但不包括动态分配的数组 arr 的大小,
// 因为数组的大小取决于它所包含的元素的数量。
// struct s 中的 int i 字段占用4个字节(在大多数现代平台上是这样),
// 而 int arr[] 是一个未指定大小的数组,所以我们不知道它占用的空间 
// 而后面 我们进行数组的使用的时候 +也就是加上想要延长的数组的长度,
// 所以这也就是为什么柔性数组是需要放到结构体最后的
int main()
{
	//这里是内存空间的扩展
	struct s* ps = (struct s*)malloc(sizeof(struct s) + 5 * sizeof(int));
	if (ps == NULL)
	{
		perror("struct:");
		return 1;
	}

	for (int i = 0; i < 5; i++)
	{
		ps->arr[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	struct  s pf = { .i = 1 };
	printf("\n%d\n", pf.i);

	//这里我们认为开辟的空间是5个整形的空间大小 所以我们需要进行这个,对这个弹性数组进行realloc
	struct s* ptr = (struct s*)realloc(ps, sizeof(struct s) + 10 * sizeof(int));
	if (ptr == NULL)
	{
		perror("realloc:");
		return 1;
	}
	ps = ptr;
	for (int i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", ps->arr[i]);
	}

	free(ps);
	return 0;
}

———————————————————————————————————————————

柔性数组扩展空间的第二种实现方案(补充说明)

这里可以放到堆上,也就是先开辟空间,因为柔性数组本身就是弹性的

然后判断是不是空指针,不是继续运行 

进入正题

这里给出五个变量,这里是单独给到柔性数组里面,和版本1不一样。

版本1里面是对整个数组进行内存延伸,这个是连续的

版本2里面是开辟一块一块的内存空间,这个不是连续的

 这样第二种会导致一种情况,

释放的时候需要先释放空间2,再释放1,需要先释放2的空间,不然是找不到2的空间,后释放ps空间。因为正常情况下1,2空间之间还有空间是不被利用的。那么你要是先释放空间1,就会导致找不到后面的空间。

但是第一种就不需要,只需要释放空间1 。

———————————————————————————————————————————

总结

还是第一种方案好

以为malloc使用次数越多

空间碎片越多

空间利用率越小

柔性数组 的优势方案 1 方便内存释放

不像第二种方案 结构体包含指针 需要释放两次

第二个方案 不是连续的空间 所以第一个使用起来方便 方便管理

文章推荐

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

内存的划分 

这里附带两章内存划分图片

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/500807.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【御控物联】JavaScript JSON结构转换(12):对象To数组——键值互换

文章目录 一、JSON结构转换是什么&#xff1f;二、核心构件之转换映射三、案例之《JSON对象 To JSON数组》四、代码实现五、在线转换工具六、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换…

智慧乡村建设新篇章:数字乡村引领农村发展新时代

目录 一、智慧乡村的内涵与建设的必要性 二、智慧乡村建设的路径探索 &#xff08;一&#xff09;加强信息基础设施建设&#xff0c;夯实智慧乡村发展基础 &#xff08;二&#xff09;推动农业智能化升级&#xff0c;提升农业生产效率和质量 &#xff08;三&#xff09;推…

Flask Python:请求上下文和应用上下文

请求上下文和应用上下文详解 一、背景二、什么是上下文2.1、请求上下文2.2、应用上下文2.3、两种上下文的底层逻辑 三、写在最后 一、背景 在如何实现异步发送邮件的时候&#xff0c;遇到过这样一个报错 RuntimeError: Working outside of request context.This typically me…

vue基础教程(4)——十分钟吃透vue路由router

同学们可以私信我加入学习群&#xff01; 正文开始 前言一、路由概念二、路由使用三、创建路由对应的组件四、给整个项目一个入口总结 前言 前面的文章运行成功后&#xff0c;页面显示如下&#xff1a; 在这个页面中&#xff0c;点击Home和About都会切换右面的页面内容&#…

PTA L2-038 病毒溯源

病毒容易发生变异。某种病毒可以通过突变产生若干变异的毒株&#xff0c;而这些变异的病毒又可能被诱发突变产生第二代变异&#xff0c;如此继续不断变化。 现给定一些病毒之间的变异关系&#xff0c;要求你找出其中最长的一条变异链。 在此假设给出的变异都是由突变引起的&a…

uniapp对接极光推送(国内版以及海外版)

勾选push&#xff0c;但不要勾选unipush 国内版 网址&#xff1a;极光推送-快速集成消息推送功能,提升APP运营效率 (jiguang.cn) 进入后台&#xff0c;并选择对应应用开始配置 配置安卓包名 以及ios推送证书&#xff0c;是否将生产证书用于开发环境选择是 ios推送证书…

C++Template<>模版的介绍及深度解析

一、泛型编程 1.什么是泛型编程 泛型编程&#xff1a;是一种程序设计方法&#xff0c;编写于类型无关的通用代码&#xff0c;实现代码复用。而模版就是泛型编程的基础和核心。 二、template<>模版 1.template模版介绍 模版&#xff0c;顾名思义就是一个模具&#xff0…

【redis】linux安装redis

目录 1. 下载redis2. 上传并解压3. 安装4. redis配置5. 启动redis-server服务 1. 下载redis 1.Redis官网2.历史版本 2. 上传并解压 1.上传到/opt/redis 2.解压 tar zxvf redis-5.0.2.tar.gz 3. 安装 1.安装gcc yum install gcc-c2.make命令 # cd /opt/redis sudo make3.…

Elment ui 动态表格与表单校验 列表数据 组件

组件做个记录&#xff0c;方便以后会用到。 效果&#xff1a; 代码 &#xff1a; <template><el-dialog title"商品详情" :visible.sync"dialogVisible" width"80%"><el-tabs v-model"activeTab"><el-tab-pane…

第十二章:预处理命令

文章目录 第十二章&#xff1a;预处理命令宏定义无参宏定义带参数的宏定义 文件包含处理 第十二章&#xff1a;预处理命令 作用&#xff1a;由编译预处理程序对程序中的特殊命令作出解释&#xff0c;以产生新的源程序对其进行正式编译 C语言与其他语言的重要区别就是可以使用预…

环境温度对测量平板有什么影响

环境温度可以对测量平板有影响。温度变化可以导致平板的尺寸发生变化。根据热膨胀原理&#xff0c;当环境温度升高时&#xff0c;平板的尺寸会扩大&#xff1b;当环境温度降低时&#xff0c;平板的尺寸会缩小。这种尺寸变化可能会导致测量结果的误差。因此&#xff0c;在测量平…

OSCP靶场--RubyDome

OSCP靶场–RubyDome 考点(CVE-2022-25765 suid ruby提权) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap -Pn -sC -sV 192.168.249.22 --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-29 00:28 EDT Nmap scan report for 192.168.249.22 Hos…

mysql 常见运算符

学习了mysql数据类型&#xff0c;接下来学习mysql常见运算符。 2&#xff0c;常见运算符介绍 运算符连接表达式中各个操作数&#xff0c;其作用是用来指明对操作数所进行的运算。运用运算符 可以更加灵活地使用表中的数据&#xff0c;常见的运算符类型有&#xff1a;算…

阿里云魔搭发起“ModelScope-Sora开源计划”,将为中国类Sora模型开发提供一站式工具链

在2024年3月23日的全球开发者先锋大会上&#xff0c;阿里云的魔搭社区宣布了一个新计划&#xff1a;“ModelScope-Sora开源计划”。这个计划旨在通过开源方式&#xff0c;帮助中国在Sora模型类型上做出更多创新。这个计划提供了一整套工具&#xff0c;包括处理数据的工具、多模…

【御控物联】 IOT异构数据JSON转化(场景案例一)

文章目录 前言技术资料 前言 随着物联网、大数据、智能制造技术的不断发展&#xff0c;越来越多的企业正在进行工厂的智能化转型升级。转型升级第一步往往是设备的智能化改造&#xff0c;助力设备数据快速上云&#xff0c;实现设备数据共享和场景互联。然而&#xff0c;在生产…

车道线检测_Canny算子边缘检测_1

Canny算子边缘检测&#xff08;原理&#xff09; Canny算子边缘检测是一种经典的图像处理算法&#xff0c;由John F. Canny于1986年提出&#xff0c;用于精确、可靠地检测数字图像中的边缘特征。该算法设计时考虑了三个关键目标&#xff1a;低错误率&#xff08;即尽可能多地检…

衰老抑制剂原知因起源金NMN热销,“海弗里克极限”将被打破?

美国著名生物学家列奥纳多 海弗里克 , 在 1961 年研究人类胎儿的细胞群体分裂次数时提出了著名的 " 海弗里克极限 " 理论。该理论认为 , 正常细胞分裂的周期是 2-3 年 , 分裂次数大概是 50 次 , 得出人类的极限寿命高达 150 岁。半个世纪后 , 世界上最长寿的人 , 打…

ssm 科研奖励申报管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 ssm 科研奖励申报管理系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用…

拆分巨石:将MVPS和MVAS应用于遗留应用程序——可持续架构(六)

前言 MVP 和 MVA 的概念不仅适用于新应用程序&#xff1b;它们提供了一种新颖的方式来审视对遗留系统的范围变更&#xff0c;以防止过快地承担过多的变化 - 参见图1。MVA 可以帮助组织评估和更新其技术标准&#xff0c;通过展示新技术如何真正对支持 MVP 至关重要。创建 MVA 可…

Flutter 使用 AndroidStudio 给(Android 安卓)进行签名方法

一、使用 AndroidStudio 创建签名 使用 AndroidStudio 打开 Flutter项目中的 android 文件夹首次打开 AndroidStudio 会加载一会。菜单栏 &#xff1a; Build -> Generate Signed Bundle APK... 选中 APK -> Next点击Create new....下面按照需求填写即可- 文件夹选择 项…