当前位置: 首页 > news >正文

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指向的原内存)。
  • 调整逻辑:
    • 若原内存后有足够空间,直接扩展,返回原地址;
    • 若空间不足,重新分配新内存块,复制原数据到新块,释放原内存,返回新地址;
    • ptrNULL,等价于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; // 释放后置空,避免悬空指针 
    

常见问题与注意事项

  1. 分配失败检查:调用malloc/calloc/realloc后,必须判断返回值是否为NULL,否则后续操作空指针会崩溃。
  2. 内存泄漏:动态分配的内存未用free释放,程序结束前不会自动回收,长期运行会耗尽内存。
  3. 悬空指针free后指针未置NULL,后续若误操作(如解引用、再次free)会触发未定义行为。
  4. 内存越界:访问分配内存范围外的地址(如p[5]但只分配了5个int,索引0~4有效),会破坏内存结构,导致程序崩溃或数据错乱。
  5. realloc的风险:若realloc失败返回NULL,原ptr仍有效,需单独处理(如备份原指针)。

C语言动态内存管理核心是malloc/calloc/realloc分配堆内存,free释放内存。需牢记分配必检查、释放要置空、避免越界/重复释放,才能安全管理内存。

指针

一、指针的基本概念

  1. 定义与声明

    指针是存储另一个变量的地址的变量。其声明形式如下:

    数据类型 *指针名;

    其中数据类型是指针指向的数据类型的说明符,而*表示这是一个指针变量。

    示例:

    int *p; // p 是一个指向整数的指针
  2. 取地址运算符(&)和解引用运算符(*)

    • &:取地址运算符,用于获取变量的地址。

      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)); // 使用指针访问数组元素
}

三、指针与函数

  1. 传递指针作为参数

    通过传递指针给函数,可以在函数内部修改调用者提供的变量的值。

    示例:

    void increment(int *n) {(*n)++;
    }int main() {int number = 10;increment(&number);// number 现在是 11
    }
  2. 返回指针

    函数可以返回一个指针,但需要小心处理动态分配的内存以避免内存泄漏。

    示例:

    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,避免未定义行为。
  • 函数指针与函数本身的区别:虽然函数名可以直接赋值给函数指针,但它们并不完全相同。函数名是编译时常量,而函数指针是一个变量,可以在运行时改变其所指向的函数。
http://www.xdnf.cn/news/1200475.html

相关文章:

  • Web开发系列-第0章 Web介绍
  • SQL注入SQLi-LABS 靶场less21-25详细通关攻略
  • Ubuntu普通用户环境异常问题
  • 数学建模——灰色关联分析
  • 三、构建一个Agent
  • OpenCv中的 KNN 算法实现手写数字的识别
  • 消息队列MQ常见问题和解决方案
  • Java面试全攻略:Spring生态与微服务架构实战
  • 新手开发 App,容易陷入哪些误区?
  • Android:Reverse 实战 part 2 番外 IDA python
  • SignalR 全解析:核心原理、适用场景与 Vue + .NET Core 实战
  • [电网备考]计算机组成与原理
  • Vue 四个map的使用方法
  • Mysql 二进制安装常见问题
  • 设备独立性软件-高速缓存与缓冲区
  • GIF图像格式
  • 水稻调控组全景的综合绘制与建模揭示了复杂性状背后的调控架构
  • springboot基于Java的人力资源管理系统设计与实现
  • Java面试新趋势:云原生与新兴框架实战解析
  • Vscode的常用快捷键(摆脱鼠标计划)
  • 24点数学游戏(穷举法求解表达式)
  • mybatis-plus逻辑删除配置
  • PROFINET转CAN通讯协议转换速通汽车制造
  • 【机器学习-3】 | 决策树与鸢尾花分类实践篇
  • 【Typora】分享一款很好用的PJ版本的Markdown编辑器
  • k8s pod生命周期、初始化容器、钩子函数、容器探测、重启策略
  • S7-1500 与 S7-1200 存储区域保持性设置特点详解
  • ESP32学习-FreeRTOS队列使用指南与实战
  • 回归预测 | MATLAB实现BiTCN双向时间卷积神经网络多输入单输出回归预测
  • 如何在 Ubuntu 24.04 或 22.04 中更改 SSH 端口