8.c语言指针
内存管理
C语言中,栈内存(局部变量)自动分配/释放,静态区(全局、静态变量)编译时分配;堆内存需手动分配/释放,核心函数有3个:
malloc
函数
- 原型:
void* malloc(size_t size);
- 功能:在堆上分配连续的
size
字节内存,不初始化内存内容。 - 返回值:成功则返回指向内存首地址的
void*
指针;失败返回NULL
(如内存不足)。 - 用法示例:
int *p = (int*)malloc(5 * sizeof(int)); // 分配能存5个int的内存(需强转) if (p == NULL) { // 检查分配失败 printf("malloc failed\n"); return -1; }
calloc
函数
- 原型:
void* calloc(size_t num, size_t size);
- 功能:在堆上分配**
num
个大小为size
的连续内存块**,且自动初始化为0。 - 返回值:成功返回内存首地址
void*
;失败返回NULL
。 - 与
malloc
的区别:calloc
会初始化内存为0,且参数是“个数+单个大小”(malloc
是总字节数)。 - 用法示例:
int *q = (int*)calloc(5, sizeof(int)); // 分配5个int,每个初始化为0 if (q == NULL) { /* 错误处理 */ }
realloc
函数
- 原型:
void* realloc(void* ptr, size_t new_size);
- 功能:调整已分配内存块的大小(基于
ptr
指向的原内存)。 - 调整逻辑:
- 若原内存后有足够空间,直接扩展,返回原地址;
- 若空间不足,重新分配新内存块,复制原数据到新块,释放原内存,返回新地址;
- 若
ptr
为NULL
,等价于malloc(new_size)
;若new_size
为0,等价于free(ptr)
(不同实现有差异)。
- 返回值:成功返回新内存首地址;失败返回
NULL
(原ptr
指向的内存不变)。 - 用法示例:
p = realloc(p, 10 * sizeof(int)); // 将p的内存从5个int扩容到10个int if (p == NULL) { /* 错误处理(原p内存仍有效) */ }
内存释放函数free
- 原型:
void free(void* ptr);
- 功能:将
ptr
指向的动态分配的堆内存归还给系统,避免内存泄漏。 - 注意点:
ptr
必须是malloc
/calloc
/realloc
返回的指针(否则行为未定义,如野指针、栈内存指针);- 释放后
ptr
变为悬空指针(指向无效内存),需手动置NULL
避免误用; - 不可重复释放同一指针(未定义行为,可能崩溃)。
- 用法示例:
free(p); p = NULL; // 释放后置空,避免悬空指针
常见问题与注意事项
- 分配失败检查:调用
malloc
/calloc
/realloc
后,必须判断返回值是否为NULL
,否则后续操作空指针会崩溃。 - 内存泄漏:动态分配的内存未用
free
释放,程序结束前不会自动回收,长期运行会耗尽内存。 - 悬空指针:
free
后指针未置NULL
,后续若误操作(如解引用、再次free)会触发未定义行为。 - 内存越界:访问分配内存范围外的地址(如
p[5]
但只分配了5个int,索引0~4有效),会破坏内存结构,导致程序崩溃或数据错乱。 - realloc的风险:若
realloc
失败返回NULL
,原ptr
仍有效,需单独处理(如备份原指针)。
C语言动态内存管理核心是malloc
/calloc
/realloc
分配堆内存,free
释放内存。需牢记分配必检查、释放要置空、避免越界/重复释放,才能安全管理内存。
指针
一、指针的基本概念
定义与声明
指针是存储另一个变量的地址的变量。其声明形式如下:
数据类型 *指针名;
其中
数据类型
是指针指向的数据类型的说明符,而*
表示这是一个指针变量。示例:
int *p; // p 是一个指向整数的指针
取地址运算符(&)和解引用运算符(*)
&
:取地址运算符,用于获取变量的地址。int a = 5; int *p = &a; // p 现在保存了 a 的地址
*
:解引用运算符,访问指针所指向的值。int value = *p; // value 将会是 5,即 p 所指向位置的值
二、指针与数组
在C语言中,数组名实际上是一个指向数组第一个元素的常量指针。这意味着你可以使用指针来遍历数组。
示例:
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 等价于 int *p = &arr[0];
for (int i = 0; i < 5; ++i) {printf("%d ", *(p + i)); // 使用指针访问数组元素
}
三、指针与函数
传递指针作为参数
通过传递指针给函数,可以在函数内部修改调用者提供的变量的值。
示例:
void increment(int *n) {(*n)++; }int main() {int number = 10;increment(&number);// number 现在是 11 }
返回指针
函数可以返回一个指针,但需要小心处理动态分配的内存以避免内存泄漏。
示例:
int* createArray(int size) {return malloc(size * sizeof(int)); }
四、指针的算术运算
指针支持加法和减法操作,允许你移动指针指向不同的内存位置。
指针加法:将指针向前移动若干个元素的位置。
int arr[] = {1, 2, 3}; int *p = arr; p++; // p 现在指向 arr[1]
指针减法:将指针向后移动若干个元素的位置或计算两个指针之间的距离。
五、多级指针
指针本身也可以有地址,即指向指针的指针或多级指针。
示例:
int a = 10;
int *p = &a; // p 是一个指向 int 的指针
int **pp = &p; // pp 是一个指向 int* 类型指针的指针
六、void指针
void*
是一种特殊类型的指针,它可以指向任何数据类型的变量,但它不能直接解引用,因为编译器不知道它实际指向的数据类型。
示例:
void* ptr;
int a = 10;
ptr = &a;
int *intptr = (int*)ptr; // 需要强制转换为具体类型才能解引用
七、指针的安全注意事项
空指针检查:在使用指针前,应该检查它是否为
NULL
。if (ptr != NULL) {// 安全使用指针 }
避免悬空指针:当指针指向的内存被释放后,该指针变成悬空指针。访问悬空指针会导致未定义行为。
正确管理动态内存:使用
malloc
,calloc
,realloc
分配内存,并确保使用free
释放不再使用的内存以避免内存泄漏。
函数指针
在C语言中,指针不仅可以指向变量,还可以指向函数。函数指针是一种特殊的指针类型,它存储的是函数的起始地址,可以通过这个指针来调用函数。
一、定义和声明函数指针
定义一个函数指针需要指定其指向的函数的返回类型以及参数列表。其基本语法如下:
返回类型 (*指针名)(参数类型列表);
例如,假设有一个返回类型为int
,接受两个int
类型参数的函数,那么对应的函数指针可以这样定义:
int add(int a, int b) {return a + b;
}int (*funcPtr)(int, int); // 定义一个函数指针
二、初始化函数指针
你可以将函数的名字赋值给函数指针,因为函数名本质上是指向函数入口点的指针。例如:
funcPtr = add; // 将add函数的地址赋给funcPtr
或者直接在声明时初始化:
int (*funcPtr)(int, int) = add;
三、通过函数指针调用函数
一旦函数指针被初始化,就可以像普通函数那样使用它来调用函数:
int result = funcPtr(3, 4); // 相当于调用add(3, 4)
printf("%d\n", result); // 输出7
四、函数指针作为参数传递
函数指针可以作为参数传递给其他函数,这在实现回调机制时非常有用。例如:
void executeOperation(int (*operation)(int, int), int a, int b) {printf("Result: %d\n", operation(a, b));
}int main() {executeOperation(add, 5, 3); // 传递add函数的指针return 0;
}
五、函数指针数组
你也可以创建一个函数指针数组,用于存储多个函数指针。这对于实现类似多态的行为很有帮助。
int subtract(int a, int b) {return a - b;
}int main() {int (*operations[2])(int, int) = {add, subtract}; // 函数指针数组int result1 = operations[0](10, 5); // 调用addint result2 = operations[1](10, 5); // 调用subtractprintf("Add: %d, Subtract: %d\n", result1, result2);return 0;
}
六、注意事项
- 类型匹配:函数指针的类型必须与它指向的函数的签名完全匹配(包括返回类型和参数列表)。
- 空指针检查:如同其他类型的指针一样,使用前应确保函数指针不是NULL,避免未定义行为。
- 函数指针与函数本身的区别:虽然函数名可以直接赋值给函数指针,但它们并不完全相同。函数名是编译时常量,而函数指针是一个变量,可以在运行时改变其所指向的函数。