首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >精通C语言(5.柔性数组和动态内存易错点)【什么?数组长度还能来回变化?!】

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

作者头像
say-fall
发布2026-01-15 10:12:06
发布2026-01-15 10:12:06
1050
举报
在这里插入图片描述
在这里插入图片描述

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


前言:

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


正文:

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

1.1 对NULL指针进行解引用

错误案例:

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

改正:

代码语言:javascript
复制
void test1()
{
	int* p = (int*)malloc(INT_MAX / 4);
	if (p != NULL)
	{
		*p = 20;
	}
	free(p);
	p = NULL;
}
1.2 对动态开辟空间的越界访问

错误案例:

代码语言:javascript
复制
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;
}

改正:

代码语言:javascript
复制
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)分配的完整内存块。

代码语言:javascript
复制
#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

错误案例:

代码语言:javascript
复制
void test3()
{
	int a = 0;
	int* pa = &a;
	free(pa);
}

改正:

代码语言:javascript
复制
除了直接去掉,这里是无法修正的,使用free时要确保是对指向动态空间的指针使用的
1.4 使用free释放一块开辟动态内存的一部分

错误案例:

代码语言:javascript
复制
void test4()
{
	int* p = (int*)malloc(40);
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		p++;
	}
	free(p);
}

改正:

代码语言:javascript
复制
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 对同一块动态内存多次释放

错误案例:

代码语言:javascript
复制
void test5()
{
	int *p = (int*)malloc(40);
	int* ptr = p;
	free(p);
	free(ptr);
}

改正:

代码语言:javascript
复制
void test5()
{
	int *p = (int*)malloc(40);
	int* ptr = p;
	free(p);
	p= NULL;//p置空的话释放ptr时候就不会出现问题了,不过一般只需要释放一次即可
	free(ptr);
	ptr = NULL;//虽然释放一次即可,但是注意要置空两次才行
}
1.6 动态开辟内存忘记释放(内存泄漏)

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

代码语言:javascript
复制
void test6()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}

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

改正:

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

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

2. 经典案例

  • 这里的经典案例都是有问题的,大家可以先试着自己思考一下,案例精析可以点击下一篇详解看
2.1 案例一
代码语言:javascript
复制
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 案例二
代码语言:javascript
复制
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 案例三
代码语言:javascript
复制
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 案例四
代码语言:javascript
复制
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 变长数组回顾
  • 变长数组
代码语言:javascript
复制
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中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做『柔性数组』成员。
代码语言:javascript
复制
struct S
{
	int n;
	int arr[];//柔性数组成员
}s;

大小:

代码语言:javascript
复制
struct S
{
	int n;
	int arr[];
}s;

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

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

3.2 柔性数组的使用

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

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

使用:

代码语言:javascript
复制
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去进行长度的变化 等等,数组名是指针!换个形式的话,也行?

代码语言:javascript
复制
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. 内存中间的区域划分

在这里插入图片描述
在这里插入图片描述

  • 本节完…
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:
  • 正文:
    • 1.常见的动态内存管理错误
      • 1.1 对NULL指针进行解引用
      • 1.2 对动态开辟空间的越界访问
      • 1.3 对非动态开辟进行free
      • 1.4 使用free释放一块开辟动态内存的一部分
      • 1.5 对同一块动态内存多次释放
      • 1.6 动态开辟内存忘记释放(内存泄漏)
    • 2. 经典案例
      • 2.1 案例一
      • 2.2 案例二
      • 2.3 案例三
      • 2.4 案例四
    • 3. 柔性数组
      • 3.1 变长数组回顾
      • 3.2 柔性数组的使用
    • 4. 内存中间的区域划分
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档