| 上一篇 | 下一篇 |
|---|---|
| 指针的补充,包括指针的类型和大小 |
目 录
- [malloc 和 free 用法(动态分配内存)](#malloc 和 free 用法(动态分配内存))
-
- [0)为什么要用 malloc](#0)为什么要用 malloc)
- 1)malloc(),分配内存
-
- [1.0)malloc 分配的内存是未初始化的](#1.0)malloc 分配的内存是未初始化的)
- 1.1)分配的内存用于数组
-
- [① 用于一维数组](#① 用于一维数组)
- [② 用于二维数组](#② 用于二维数组)
- [③ 完整示例](#③ 完整示例)
- 1.2)分配的内存用于结构体/结构体数组
- 1.3)分配的内存用于字符串
- 2)free(),释放内存
malloc 和 free 用法(动态分配内存)
定义一个长度的变量时,一般就不需要用 malloc ,除非有动态分配内存的硬性要求。一般都是用 malloc 分配一个数组的内存空间。
💥 需要引用 #include <stdlib.h> 头文件(standard library 的缩写) 💥
0)为什么要用 malloc
这就涉及到 动态内存分配 了,动态分配的内存都位于堆上:
- 栈内存(局部变量) 在函数返回后自动销毁,不能用于返回数组。
- 全局变量 /
static变量虽生命周期长,但有严重局限 :- 不可重入:多次调用函数会互相覆盖结果(所有调用共享同一块内存);
- 线程不安全:多线程环境下会导致数据竞争;
- 堆内存 (
malloc分配)由程序员控制,可在运行时动态申请任意大小的内存,并跨函数使用; - 只有
malloc能保证 :- 每次调用分配独立内存;
- 返回的指针可被调用者安全使用并
free; - 支持运行时才确定大小的数据结构(如动态数组、链表等)。
1)malloc(),分配内存
申请的内存可用于各种类型数据
malloc:memory allocate.
功能: 在程序运行时(而不是编译时)向操作系统申请一块指定大小的连续内存空间(位于堆上),并返回指向这块内存起始地址的指针 。
函数原型(助了解):
c
void malloc(size_t _Size)
-
返回值:返回一个
void*指针,指向新分配的内存块的首地址- 函数原型的返回值类型是
void*,这意味着我们在调用malloc的时候,可以将其强制转换成任意类型去使用。 - 失败(如内存不足):返回
NULL,一般调用之后判断一下结果是否为 NULL 。
- 函数原型的返回值类型是
-
参数:要申请的字节数。
基础语法(√):
c
/* 原始写法 */
元素类型* 指针名 = (元素类型*)malloc(总字节数); // 这种写法需要自己根据所用系统的规则,计算总字节数,比较生硬,易出错
/* 高效写法 */
元素类型* 指针名 = (元素类型*)malloc(sizeof(元素类型)*个数); //sizeof(元素类型)*个数 = 总字节数, sizeof(元素类型): 获取元素类型所占字节数
在申请内存之后,要判断一下 malloc 分配内存是否成功:
c
if (指针名==NULL)
{
printf("malloc failed\n");
return -1;
}
如果不成功,main() 函数就直接返回 -1(返回啥不重要),中断函数。
1.0)malloc 分配的内存是未初始化的
malloc 分配的内存内容是未初始化的 (里面是垃圾值)。如果需要初始化为 0,可以使用 calloc ,也可以挨个赋值。
也可以使用 memset() 函数,要引入 #include <string.h> 头文件:
c
#include <string.h>
int *presult = malloc(m * p * sizeof(int));
memset(presult, 0, m * p * sizeof(int)); // 参数含义依次为:首地址,用什么值覆盖,覆盖字节数是多少
1.1)分配的内存用于数组
① 用于一维数组
申请内存,示例:
- 申请 10 个字符数组空间:
char* pchar = (char*)malloc(sizeof(char)*10); - 申请 10 个整数数组空间:
int* pint = (int*)malloc(sizeof(int)*10); - 原始写法,以 64 位系统为例(一般不用):
- 申请 10 个整数数组空间:
int* pint = (int*)malloc(40); - 申请 10 个 double 类型数组空间:
double* pdouble = (double*)malloc(80);
- 申请 10 个整数数组空间:
那申请之后,该如何使用/遍历呢?
首先我们要知道三点:①指针也是有类型的;②指针也是可以做指针运算(+/-/++/--)的;③数组名就是指向数组首地址的常量指针。具体使用可参考博客:C++ 学习与 CLion 使用:(十)★指针★,......指针运算、动态内存分配、数组元素的移除与插入...... ,看链接中的 5),C 和 C++ 的指针操作是一样的。
不使用 malloc 的时候,我们比如说是定义一个数组 int a[10]; ,然后后续可以直接用 a[0]、a[1] 表示数组元素。
其实 int* pint = (int*)malloc(sizeof(int)*10); 就相当于:
c
int a[10];
int *pa = a; // int *pa = &a[0];
pint 就等同于 a ,区别是后者被分配的内存位于栈上,而 malloc 分配的内存位于堆上。
我们仍以 int* pint = (int*)malloc(sizeof(int)*10); 为例:
pint 可以直接当数组名使用,即: pint[0]、pint[1]、......、pint[9] ,pint[i] 中的 i=0~元素个数-1 。但索引不能越界 !
② 用于二维数组
申请用于二维数组的内存,代码会复杂一些,语法:
c
/* 高效写法 */
数组类型 (*指针名)[列数] = (数组类型*)malloc(sizeof(数组类型)*行数*列数); // sizeof(数组类型)*行数*列数 = 总字节数
示例:
- 申请一个 2×3 的二维字符数组空间:
char (*pchar)[3] = (char*)malloc(sizeof(char)*2*3); - 申请一个 2×3 的二维整数数组空间:
int (*pint)[3] = (int*)malloc(sizeof(int)*2*3);
那申请之后,该如何使用/遍历呢?
和一维数组的基本一致,我们以 int (*pint)[3] = (int*)malloc(sizeof(int)*2*3); 为例:
pint 可以直接当数组名使用,即: pint[0][0]、pint[0][1]、......、pint[1][2] ,pint[i][j] 中的 i=0~行数-1,j=列数-1 。但索引不能越界 !
③ 完整示例
c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
/* 分配用于长度为3的一维整型数组的空间 */
int *parry1 = (int*)malloc(sizeof(int)*3);
if (parry1 == NULL) {
printf("malloc failed\n");
return -1;
}
// 赋值(加个类型转换, 加强一下代码能力)
for (char i=0; i<3; i++)
{
parry1[i]=(int)i;
}
printf("parry1 address: %p\n", parry1);
printf("parry1 elements: %d %d %d\n", parry1[0],parry1[1],parry1[2]);
/* 释放内存 */
free(parry1);
printf("----------------------------------\n");
/* 分配用于长度为2×3的二维整型数组的空间 */
int (*parry2)[3] = (int*)malloc(sizeof(int)*2*3);
if (parry2 == NULL) {
printf("malloc failed\n");
return -1;
}
// 赋值(加个类型转换, 加强一下代码能力)
for (char i=0; i<2; i++)
{
for (char j=0; j<3; j++)
parry2[i][j]=(int)i+(int)j;
}
printf("parry2 address: %p\n", parry2);
printf("parry1 elements: %d %d %d ... %d \n", parry2[0][0],parry2[0][1],parry2[0][2],parry2[1][2]);
free(parry2);
return 0;
}
运行结果为:
c
parry1 address: 000001b8c2f21440
parry1 elements: 0 1 2
----------------------------------
parry2 address: 000001b8c2f21440
parry1 elements: 0 1 2 ... 3
1.2)分配的内存用于结构体/结构体数组
在 C 语言中,结构体的完整类型名是
struct 结构体名,而不是单单的结构体名.
基础语法(√):
c
/* 先定义一个结构体类型 */
struct 结构体名
{
结构体成员1;
结构体成员2;
...
};
/* 高效写法 */
struct 结构体名* 指针名 = (struct 结构体名*)malloc(sizeof(struct 结构体名)); // 分配一个结构体实例
/* 扩展:分配 n 个结构体(结构体数组) */
struct 结构体名* 指针名 = (struct 结构体名*)malloc(sizeof(struct 结构体名) * n); // 相当于动态创建 struct 结构体名 数组[n]
分配+使用步骤总结:
- 先定义结构体类型(如
struct Student); - 用
malloc+sizeof(struct 类型)分配内存; - 检查返回值是否为
NULL(防内存分配失败); - 通过
->或[i].成员访问成员(和数组很像,直接把指针名当结构体名使用); - 使用完毕调用
free(指针名)释放内存; - (可选但推荐)将指针置为
NULL,避免悬空指针。
完整示例:
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 先定义结构体类型:学生 */
struct Student {
int id;
char name[32];
};
int main() {
// 分配 2 个学生的内存
struct Student* students = (struct Student*)malloc(sizeof(struct Student) * 2);
if (students==NULL)
{
printf("malloc failed\n");
return -1;
}
// 赋值
students[0].id = 1;
strcpy(students[0].name, "王五");
students[1].id = 2;
strcpy(students[1].name, "赵六");
// 读取并打印
for (int i = 0; i < 2; i++) {
printf("学生 %d: ID=%d, 姓名=%s\n", i, students[i].id, students[i].name);
}
// 释放
free(students);
students = NULL;
return 0;
}
运行结果如下:
c
学生 0: ID=1, 姓名=王五
学生 1: ID=2, 姓名=赵六
1.3)分配的内存用于字符串
基础语法(√):
c
char *指针名 = (char*)malloc(sizeof(char)*字符个数)
/* 由于char就占一个字节, 所以用原始写法更简单, 但为了代码的统一性,也可以用高效写法 */
char *指针名 = (char*)malloc(字符个数)
分配完内存之后,使用 strcpy 函数给字符串赋值(引用 #include <string.h> 头文件)
c
strcpy(指针名, "字符串内容");
完整示例:
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char *s = (char*)malloc(10);
strcpy(s, "Hello");
printf("%s\n", s);
free(s);
return 0;
}
给其他的类型分配内存大差不差......
2)free(),释放内存
函数原型:
c
void free(void* _Block);
- 参数:malloc 申请的内存的 首地址
函数使用:
c
/* malloc 分配内存, 高效写法 */
元素类型* 指针名 = (元素类型*)malloc(sizeof(元素类型)*个数); //sizeof(元素类型)*个数 = 总字节数
/* 使用 free 函数释放内存 */
free(指针名); // 这个指针名就是首地址
注意:
- malloc 后,如果这块内存要做的事情做完了,但是没有 free ,操作系统就不敢乱动,这块空间就没法做其他事,久而久之,可用的内存就会变少,这就是 内存泄漏(内存丢失);
- 如果 malloc 后,事情还没做呢,就将指针又 malloc 到其他内存了,那么原来 malloc 的内存就荒废了,也会造成内存泄漏(内存丢失);
- 错误 free(如 free 栈/全局变量),会导致 程序崩溃 ;
- 不能重复 free ,已经释放过的内存,操作系统(电脑)可能会分配给其他程序了,如果再 free 的话,会导致电脑紊乱。
free 之后,建议令 指针名 = NULL; ,避免悬空指针。