精通C语言(5.柔性数组和动态内存易错点)【什么?数组长度还能来回变化?!】

在这里插入图片描述
🌈这里是say-fall分享,感兴趣欢迎三连与评论区留言
🔥专栏:《C语言入门知识点》《C语言底层》《精通C语言》《C语言编程实战》
💪格言:做好你自己,你才能吸引更多人,并与他们共赢,这才是你最好的成长方式。


前言:

上一篇是关于动态内存管理的四个函数的文章,那这一篇我们就来了解一下柔性数组和一些常见的动态内存管理时候的错误,帮助大家减少这些错误的出现,同时也给大家几个小练习,看看大家能不能找到这些练习中的瑕疵吧~



正文:

1.常见的动态内存管理错误

1.1 对NULL指针进行解引用

错误案例:

void test1()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;//这里如果malloc返回的是NULL,就会导致错误
	free(p);
}

改正:

void test1()
{
	int* p = (int*)malloc(INT_MAX / 4);
	if (p != NULL)
	{
		*p = 20;
	}
	free(p);
	p = NULL;
}

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

错误案例:

void test2()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	int i = 0;
	for (i = 0;i < 11;i++)//注意这里会导致超出开辟的空间,即越界访问
	{
		*(p + i) = 1;
	}
	free(p);
	p = NULL;
}

改正:

void test2()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		*(p + i) = 1;
	}
	free(p);//这里能直接返回p吗?这个改正修改对了吗
	p = NULL;
}

这里提示一下:可以用另一个指针 p 接收 malloc 返回的动态内存指针 ptr,之后对 p 执行 free 操作是合法且正确的,只要保证 p 指向的是 malloc(或 calloc/realloc)分配的完整内存块。

#include <stdlib.h>

int main() {
    int* ptr = (int*)malloc(10 * sizeof(int));  // 分配内存,ptr 指向该内存
    int* p = ptr;  // p 接收 ptr 的地址,此时 p 和 ptr 指向同一块内存
    
    // 使用内存...
    
    free(p);  // 正确:释放 p 指向的内存(与 ptr 指向的是同一块)
    p = NULL;  // 好习惯:释放后置空,避免野指针
    ptr = NULL;  // 同理,ptr 也需置空(此时它已指向被释放的内存)
    return 0;
}

1.3 对非动态开辟进行free

错误案例:

void test3()
{
	int a = 0;
	int* pa = &a;
	free(pa);
}

改正:

除了直接去掉,这里是无法修正的,使用free时要确保是对指向动态空间的指针使用的

1.4 使用free释放一块开辟动态内存的一部分

错误案例:

void test4()
{
	int* p = (int*)malloc(40);
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		p++;
	}
	free(p);
}

改正:

void test4()
{
	int* p = (int*)malloc(40);
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		p++;
	}
	p = p - 10;
	free(p);
	p = NULL;
}//这些案例操作有些是无意义的,只是为了表现错误的特性
//注意,上面 1.2 修改完是不是犯了这个错误呢?

1.5 对同一块动态内存多次释放

错误案例:

void test5()
{
	int *p = (int*)malloc(40);
	int* ptr = p;
	free(p);
	free(ptr);
}

改正:

void test5()
{
	int *p = (int*)malloc(40);
	int* ptr = p;
	free(p);
	p= NULL//p置空的话释放ptr时候就不会出现问题了,不过一般只需要释放一次即可
	free(ptr);
	ptr = NULL//虽然释放一次即可,但是注意要置空两次才行
}

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

所谓的内存泄漏会导致什么呢?其实就是内存一点点被吃掉,尤其是在一些循环的地方。
错误案例:

void test6()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}

int main()
{
	while (1)
	{
		test();
	}
}//这里的test6中就有空间不释放,如果这个代码运行起来,循环一次就吃掉一点空间,这是很致命的

改正:

void test6()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
	free(p);
	p = NULL;//在动态内存的使用中,最重要的就是释放内存和置空操作
}

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

2. 经典案例

  • 这里的经典案例都是有问题的,大家可以先试着自己思考一下,案例精析可以点击下一篇详解看

2.1 案例一

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void Test1(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}
int main()
{
	Test1();
	return 0;
}

2.2 案例二

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

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

2.3 案例三

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;
}

2.4 案例四

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
int main()
{ 
	Test();
	return 0;
}

3. 柔性数组

记得我们之前了解过的变长数组吗?是的,这个柔性数组和那个不一样。

3.1 变长数组回顾

  • 变长数组
int main() {
    int n;
    printf("请输入数组长度:");
    scanf("%d", &n);

    int arr[n];  // 变长数组:长度由变量 n 决定(运行时确定)

    // 使用变长数组
    
    return 0;
}
//但是有的编译器是不能使用变长数组的,例如vs2022
  • 特点:
  1. 长度在运行时确定(n
  2. 存储在栈上,而非堆区(malloc申请的空间在堆区)
  3. 不能初始化,例如:int arr[n] = {0}; 是错误的
  • 柔性数组
    C99中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做『柔性数组』成员。
struct S
{
	int n;
	int arr[];//柔性数组成员
}s;

大小:

struct S
{
	int n;
	int arr[];
}s;

int main()
{
	printf("%zd\n", sizeof(s));
	return 0;
}//输出结果是4

也就是说:柔性数组成员是不占空间大小的,那要怎么使用呢?

3.2 柔性数组的使用

先看一下柔性数组的特点:

  • 柔性数组的特点:
  1. 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
  2. sizeof返回的这种结构⼤⼩不包括柔性数组的内存。
  3. 包含柔性数组成员的结构⽤malloc()进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。

使用:

typedef struct S
{
	int i;
	int arr[];
}s;

int main()
{
	printf("%zd\n", sizeof(s));
	s* p = (s *)malloc(4 + 10 * sizeof(int));//拓展空间
	
	//使用柔性数组

	p->n = 10;
	for (i = 0;i < 10;i++)
	{
		p->arr[i] = i + 1;
	}
	//内存释放
	free(p);
	p = NULL;
	return 0;
}

能看得出来,柔性数组的使用是要先开辟空间的,然后就可以正常使用了,也可以搭配realloc去进行长度的变化
等等,数组名是指针!换个形式的话,也行?

typedef struct S
{
	int i;
	int * ps;
}s;

int main()
{
	printf("%zd\n", sizeof(s));
	s* p = (s*)malloc(sizeof(s));//动态分配结构体内存
	p -> i = 100;//i = 100
	int* ps = (int*)malloc(p->i * sizeof(int));//为指针指的地址分配对应个空间
	
	//使用柔性数组
	for(i=0; i<100; i++)
	{ 
		p->ps[i] = i;
 	}
	
	//内存释放
	free(p -> ps);//先要释放后申请的空间
	p -> ps = NULL;
	free(p);//再释放前面申请的空间
	return 0;
}

这两种方法都可以达到相同的效果,但是第一种方法相比第二种方法有一些优点:

  1. ⽅便内存释放,只需要释放一次就可以了
  2. 这样有利于访问速度,在两次申请动态空间的过程中会造成更多间断的空间,空间不连续就会导致访问的速度慢一些

4. 内存中间的区域划分

在这里插入图片描述


  • 本节完…
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值