首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C语言动态内存:malloc/free 用不对,程序秒变内存黑洞!

C语言动态内存:malloc/free 用不对,程序秒变内存黑洞!

作者头像
码途随笔
发布2026-01-12 19:50:12
发布2026-01-12 19:50:12
1110
举报

一、动态内存管理

以前向内存申请空间时(两种方式)

  • int a;//开辟几个字节的方式
  • int a[10];//开辟连续的空间 缺点:一旦申请好空间后,大小是无法修改的

如果想描述一个班的成绩,如果几个班人数不相同,只能用int arr[30]来描述吗?

1.1 malloc

申请一块连续的空间,返回指向这块空间的指针

内存布局:

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

1.2 free

给free传动态空间起始地址,把空间的使用权限还给了操作系统

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
#include<stdlib.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		*(p + i) = i+1;
	}
	free(p);
	p = NULL;
}

1.3 calloc和realloc

calloc calloc与malloc不同的是参数部分,并且calloc会初始化空间为零

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

realloc

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

具体是如何调整的?

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

错误示例:

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

如果realloc调整失败,返回空指针,那原来的空间也找不到了 当realloc传参部分是NULL时,功能相当于malloc

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

1.4 常见的错误

  • 对NULL解引用操作(需要提前判断像assert或if语句判断)
在这里插入图片描述
在这里插入图片描述
  • 对动态开辟的内存越界访问
在这里插入图片描述
在这里插入图片描述
  • 对非动态开辟内存进行free释放
在这里插入图片描述
在这里插入图片描述
  • free释放开辟内存的一部分
在这里插入图片描述
在这里插入图片描述
  • 对同一块内存空间的多次释放
在这里插入图片描述
在这里插入图片描述

可以在中间把p赋值为NULL,free(NULL)什么事都不做

  • 动态内存忘记释放(导致内存泄露)
在这里插入图片描述
在这里插入图片描述

另外:

  • 其实malloc/calloc/realloc申请的内存,如果不想使用的时候可以使用free释放
在这里插入图片描述
在这里插入图片描述
  • 如果没有使用free来说释放,当程序运行结束的时候,也会由操作系统回收的!

1.5 题目解析

第一题: 错误案例

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

方法一:

代码语言:javascript
复制
void Getmemory(char **p)
{
	*p = (char*)malloc(100);
}
int main()
{
	char * str = NULL;
	Getmemory(&str);
	strcpy(str, "hellow word");
	printf("%s", str);
	free(str);
	str = NULL;
	return 0;
}

方法二:

代码语言:javascript
复制
char* Getmemory()
{
	char *p = (int*)malloc(100);
	return p;
}
int main()
{
	char * str = NULL;
	str = Getmemory();
	strcpy(str, "hellow word");
	printf("%s", str);
	free(str);
	str = NULL;
	return 0;
}

第二题:

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

栈可以返回值,但是不能返回地址 第三题:避免内存泄漏的问题

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

第四题:free完并没有初始化为空指针,要手动初始化为空指针

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
int main()
{
	char* p = (char*)malloc(20);
	if (p == NULL)
	{
		perror("malloc");
	}
	strcpy(p, "hellow");
	printf(p);
	free(p);
	p = NULL;
	return 0;
}

1.6 柔性数组

在结构体中,最后一个成员,并且最后一个成员是数组,数组没有指定大小,这个时候是柔性数组 两种写法:

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

柔性数组的特点:

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

”柔性“由来: 正常开辟空间: struct S s1; 而柔性数组不是这样开辟空间的,是创建在堆上,用malloc来开辟

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
struct S
{
	int n;
	int arr[];
};
int main()
{
	struct S* p = (struct S*)malloc(sizeof(int) + 5 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	p->n = 100;
	for (int i = 0; i < 5; i++)
	{
		p->arr[i] = i + 1;
	}
	struct S* ptr = (struct S*)realloc(p, sizeof(int) + 10 * sizeof(int));
	if (ptr != NULL)
	{
		p = ptr;
		for (int i = 5; i < 10; i++)
		{
			p->arr[i] = i + 1;
		}
	}
	free(p);
	p = NULL;
}

其他方法:

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
struct S
{
	int n;
	int* arr;
};
int main()
{
	struct S* p = (struct S*)malloc(sizeof(struct S));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	p->arr = (int *)malloc(5 * sizeof(int));
	if (p->arr == NULL)
	{
		perror("malloc");
		return 2;
	}
	p->n = 100;
	for (int i = 0; i < 5; i++)
	{
		p->arr[i] = i + 1;
	}
	int* ptr = (int*)realloc(p->arr, 10 * sizeof(int));
	if (ptr != NULL)
	{
		p->arr = ptr;
		for (int i = 5; i < 10; i++)
		{
			p->arr[i] = i + 1;
		}
	}
	free(p->arr);
	free(p);
	return 0;
}

哪一种方案更好?

第一种

  • 使用柔性数组不容易出错,一次malloc,一次free就行了,而用指针的方式,多次使用malloc和free(并且还要考虑释放顺序问题)
  • 第一种内存碎片越少,利用率较高
  • 第一种连续内存访问速度高

1.7 内存区域划分

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

二、文件操作

文件可以让数据持久性的保存,文件一般分为程序文件(后缀为.c、.obj、.exe)和数据文件(写程序操作的文件)

2.1 二进制文件与文本文件

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

2.2 文件的打开与关闭

文件的操作与喝一瓶水类似:

  • 开瓶盖
  • 喝水
  • 关瓶盖

流的概念 由于不同的外部设备不同的操作方式不一样,为了简化程序员学习的难度,引入流的概念

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

一般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作。 C语言程序启动时默认会打开流

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

C语言中就是通过文件指针来维护各种流的操作 每一个被使用的文件在内存中开辟了文件信息区,用来存放与文件相关的信息(文件名、文件位置、文件状态),这些信息保存在结构体变量中,结构体类型取名为FILE 不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够间接找到与它关联的文件 每一个文件都有文件信息区和指向文件信息区的文件指针 两个关键的函数

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

2.3 文件顺序读写

2.3.1 以文本方式
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以写文件为例(fputc)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
int main()
{
	FILE* p = fopen("test.txt", "w");
	for (char ch = 'a'; ch <= 'z'; ch++)
	{
		fputc(ch, p);
	}
	fclose(p);
	p = NULL;
	return 0;
}

读文件为例(fgetc)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
int main()
{
	FILE* p = fopen("test.txt", "r");
	int ch = 0;
	while ((ch=getc(p))!= EOF)
	{
		printf("%c", ch);
	}
	fclose(p);  
	p = NULL;
	return 0;
}

写文件(fputs)

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
int main()
{
	FILE* p = fopen("test.txt", "w");
	if (p == NULL)
	{
		perror("fopen");
	}
	fputs("abde\n", p);
	fputs("haha", p);
	fclose(p);
	p = NULL;
	return 0;
}

读文件(fgets),不会把\0写进去,并且只读一行,换行不会读取(长度允许的话,会把\n读取,再补\0)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
int main()
{
	FILE* p=fopen("test.txt", "r");
	if (p == NULL)
	{
		perror("fopen");
		return 1;
	}
	char arr[20] = { 0 };
	while (fgets(arr, 10, p) != NULL)
	{
		printf("%s", arr);
	}
	fclose(p);
	p = NULL;
	return 0;
}
在这里插入图片描述
在这里插入图片描述
  • sscanf与sprintf
代码语言:javascript
复制
#include<stdio.h>
struct S
{
	int age;
	char name[10];
};
int main()
{
	struct S s1 = { 18,"zhangshan" };
	char arr[100] = { 0 };
	sprintf(arr, "%d %s", s1.age, s1.name);
	printf("%s\n", arr);

	struct S p1 = { 0 };
	sscanf(arr, "%d%s", &(p1.age), p1.name);
	printf("%d %s", p1.age, p1.name);
}
2.3.2 以二进制方式
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
int main()
{
	FILE*p=fopen("text.txt", "wb");
	if (p == NULL)
	{
		perror("fopen");
	}
	int arr[] = { 1,2,3};
	fwrite(arr, 4, 3, p);
	fclose(p);
	return 0;
}
在这里插入图片描述
在这里插入图片描述

返回值是成功读取到的个数

代码语言:javascript
复制
int main()
{
	FILE* p = fopen("text.txt", "rb");
	if (p == NULL)
	{
		perror("fopen");
	}
	int arr[5] = { 0 };
	fread(arr, sizeof(int), 5, p);
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

2.4 文件随机读写

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

控制指针位置

代码语言:javascript
复制
int main()
{
	FILE*p=fopen("text.txt", "r");
	if (p == NULL)
	{
		perror("fopen");
	}
	char ch = fgetc(p);
	printf("%c ", ch);
	fseek(p, -4, SEEK_END);
	ch = fgetc(p);
	printf("%c ", ch);
	fseek(p, 2, SEEK_SET);
	ch = fgetc(p);
	printf("%c ", ch);
	return 0;
}
在这里插入图片描述
在这里插入图片描述

计算偏移量

代码语言:javascript
复制
int main()
{
	FILE*p=fopen("text.txt", "r");
	if (p == NULL)
	{
		perror("fopen");
	}
	fseek(p, 0, SEEK_END);
	int ret = ftell(p);
	printf("%d", ret);
	return 0;
}
在这里插入图片描述
在这里插入图片描述

文件指针位置回到起始位置

代码语言:javascript
复制
int main()
{
	FILE*p=fopen("text.txt", "r");
	if (p == NULL)
	{
		perror("fopen");
	}
	fseek(p, 0, SEEK_END);
	rewind(p);
	char ch = 0;
	ch = fgetc(p);
	printf("%c", ch);
	return 0;
}

2.5 文件读取结束的判定

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

当判断是什么原因导致文件结束的可以用以下函数

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
int main()
{
	FILE* p=fopen("text.txt", "w");
	if (p == NULL)
	{
		perror("fopen");
	}
	int ch = 0;
	while ((ch = fgetc(p)) != EOF)
	{
		printf("%c\n", ch);
	}
	if (feof(p))
	{
		printf("遇到文件末尾\n");
	}
	else if(ferror(p))
	{
		perror(NULL);
	}
	return 0;
}
在这里插入图片描述
在这里插入图片描述

2.6 文件缓冲区

因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、动态内存管理
    • 1.1 malloc
    • 1.2 free
    • 1.3 calloc和realloc
    • 1.4 常见的错误
    • 1.5 题目解析
    • 1.6 柔性数组
    • 1.7 内存区域划分
  • 二、文件操作
    • 2.1 二进制文件与文本文件
    • 2.2 文件的打开与关闭
    • 2.3 文件顺序读写
      • 2.3.1 以文本方式
      • 2.3.2 以二进制方式
    • 2.4 文件随机读写
    • 2.5 文件读取结束的判定
    • 2.6 文件缓冲区
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档