当前位置: 首页 > 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.dtcms.com/a/300846.html

相关文章:

  • 标签驱动的可信金融大模型训练全流程-Agentar-Fin-R1工程思路浅尝
  • AI驱动的金融推理:Fin-R1模型如何重塑行业决策逻辑
  • JSON格式化与结构对比
  • 2025年量子计算与前沿技术融合:六大变革性方向深度解析
  • Rust实战:高效开发技巧
  • 02人工智能中优雅草商业实战项目视频字幕翻译以及声音转译之以三方AI模型API制作方式预算-卓伊凡|莉莉
  • 【在Unity游戏开发中Dictionary、List介绍】
  • 基于Springboot+UniApp+Ai实现模拟面试小工具七:前端项目创建及框架搭建
  • 深入理解 Spring 中的 XmlBeanFactory 原理及实践
  • 【最新版】防伪溯源一体化管理系统+uniapp前端+搭建教程
  • ArKTS:List 数组
  • 机器学习特征选择 explanation and illustration of ANOVA
  • ROS2总结(二)
  • UDS 0x29 身份验证服务 Authentication service
  • Rust Web 全栈开发(十一):WebAssembly 尝鲜
  • 2507rust,rust写驱动
  • rust- 定义模块以控制作用域和隐私
  • 无刷电机三项霍尔连接线序组合详细分析与波形实例
  • ETF历史每笔成交分钟级高频数据深度解析
  • 墨者:通过手工解决SQL手工注入漏洞测试(MongoDB数据库)
  • Rust与Java DynamoDB、MySQL CRM、tokio-pg、SVM、Custors实战指南
  • 零基础 “入坑” Java--- 十四、字符串String
  • mybatis-plus实体类主键生成策略
  • 使用uni-app开发一个点餐收银台系统前端静态项目练习
  • 车辆网络安全规定之R155与ISO/SAE 21434
  • 09_opencv_遍历操作图像像素
  • uniapp input 聚焦时键盘弹起滚动到对应的部分
  • 基础配置介绍,VLAN配置,DHCP配置
  • 迷宫生成与路径搜索(A算法可视化)
  • SparkSQL — get_json_object函数详解(解析 json)