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

【C语言】第四课 指针与内存管理

1 指针的本质:地址与解引用

1.1 什么是指针?

指针是一个变量,其存储的值是另一个变量的内存地址。你可以将内存想象成一个巨大的公寓楼,每个字节是一个房间,每个房间都有唯一的门牌号(地址)。指针就是记录着这些门牌号的便签。

  • 声明指针数据类型 *指针变量名;

    int *p;      // 指向整型的指针
    char *ch;    // 指向字符型的指针
    float *fp;   // 指向浮点型的指针
    

    * 表示这是一个指针变量,数据类型 说明了指针所指向的内存区域中存储的数据类型。

  • 初始化指针:使用 &(取地址操作符)获取变量的地址。

    int a = 10;
    int *p = &a;  // p 指向变量a的地址
    

    未初始化的指针是“野指针”,指向随机内存,非常危险。良好的习惯是定义时立即初始化,若暂无明确指向,可初始化为 NULL(或 0)。

    int *safe_ptr = NULL; // 安全的初始化
    
  • 解引用指针:使用 *(解引用操作符)访问或修改指针所指向地址的值。

    printf("a = %d\n", a);   // 输出: a = 10
    printf("*p = %d\n", *p); // 输出: *p = 10 (通过p访问a的值)*p = 20; // 通过指针p修改其指向地址(即变量a)的值
    printf("a is now %d\n", a); // 输出: a is now 20
    

    解引用本质上是一次内存访问。对 *p 的操作就是对 p 所存地址处数据的操作。

1.2 指针的大小

指针变量的大小是固定的,取决于系统的寻址能力,与它指向的数据类型无关:

  • 32位系统:通常为 4字节
  • 64位系统:通常为 8字节
    你可以用 sizeof 操作符验证:
printf("Size of int pointer: %zu\n", sizeof(int*));    // 输出: 8 (在64位系统)
printf("Size of char pointer: %zu\n", sizeof(char*));  // 输出: 8
printf("Size of double pointer: %zu\n", sizeof(double*)); // 输出: 8

2 指针的算术运算

指针的算术运算(加减)不是简单的整数加减,而是以所指向数据类型的大小为单位进行移动。

int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // p 指向数组首元素(arr[0])的地址printf("*p = %d\n", *p);     // 输出: 10
printf("Address: %p\n", p);p++; // 向后移动一个int单位(通常是4字节),指向arr[1]
printf("After p++:\n");
printf("*p = %d\n", *p);     // 输出: 20
printf("Address: %p\n", p); // 地址值比之前增加了4p += 2; // 向后移动两个int单位,指向arr[3]
printf("After p += 2:\n");
printf("*p = %d\n", *p);     // 输出: 40

减法运算可以计算两个指针之间的距离(元素个数):

int *p1 = &arr[0];
int *p2 = &arr[3];
ptrdiff_t diff = p2 - p1; // 计算两个指针之间相差的元素个数
printf("p2 - p1 = %td\n", diff); // 输出: 3 (相差3个元素)

注意:指针减法的两个指针必须指向同一块连续内存空间(如同一个数组),否则行为未定义。

3 指针与数组

在C语言中,数组名在大多数情况下是一个指向数组首元素的常量指针

int arr[5] = {1, 2, 3, 4, 5};// 以下访问方式是等价的:
printf("arr[0] = %d\n", arr[0]);
printf("*arr = %d\n", *arr); // 对数组名解引用访问第一个元素// 通过指针算术访问数组元素
printf("arr[1] = %d\n", *(arr + 1)); 
printf("arr[2] = %d\n", *(arr + 2));// 定义一个指针遍历数组
int *ptr = arr;
for (int i = 0; i < 5; i++) {printf("Element %d: %d\n", i, *(ptr + i));// 或 printf("Element %d: %d\n", i, ptr[i]); // 指针也可以使用下标!
}

重要区别:数组名是指针常量,其值(指向的地址)不可改变。而指针变量可以重新赋值。

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;// p = p + 1;  // 合法,p现在指向arr[1]
// arr = arr + 1; // 非法!编译错误!数组名是常量,不能修改。

4 指针与函数

4.1 指针作为函数参数(模拟“传引用”)

C语言函数参数传递默认是传值调用,即函数获得的是实参值的副本。修改副本不会影响原始实参。若希望函数内部修改外部变量的值,需要传递变量的指针

// 一个交换两个变量值的函数
void swap(int *x, int *y) { // 接收指针作为参数int temp = *x; // 解引用x,获取其指向的值*x = *y;       // 将y指向的值赋给x指向的内存*y = temp;     // 将temp的值赋给y指向的内存
}int main() {int a = 10, b = 20;printf("Before swap: a = %d, b = %d\n", a, b);swap(&a, &b); // 传递变量a和b的地址printf("After swap: a = %d, b = %d\n", a, b);return 0;
}

输出:

Before swap: a = 10, b = 20
After swap: a = 20, b = 10

工作原理:函数 swap 接收的是 ab 的地址(指针),通过解引用操作 *x*y,直接操作 main 函数栈帧中 ab 所在的内存单元,从而真正交换它们的值。

4.2 数组作为函数参数

当数组作为函数参数传递时,它会退化为指向其首元素的指针。因此,函数内部无法通过 sizeof 获取原始数组的长度。

void printArray(int arr[], int size) { // int arr[] 等价于 int *arr// 在函数内部,sizeof(arr) 将是指针的大小(8或4),而不是整个数组的大小!for (int i = 0; i < size; i++) {printf("%d ", arr[i]); // 虽然arr是指针,但仍可使用下标语法}printf("\n");
}int main() {int myArray[] = {1, 2, 3, 4, 5};int length = sizeof(myArray) / sizeof(myArray[0]); // 正确计算数组长度printArray(myArray, length); // 传递数组名和实际长度return 0;
}

5 动态内存分配:堆(Heap)

5.1 堆与栈的区别

理解的区别至关重要。

特性栈 (Stack)堆 (Heap)
管理方式编译器自动分配和释放程序员手动分配 (malloc, calloc) 和释放 (free)
生命周期函数执行期间,函数返回后自动销毁从分配开始直到显式释放为止
大小限制较小(例如几MB),操作系统依赖很大,仅受系统可用虚拟内存大小限制
分配速度非常快相对较慢,涉及更复杂的管理
碎片化可能产生碎片
灵活性大小和生命周期在编译时确定大小和生命周期在运行时动态决定

5.2 动态内存分配函数

C语言使用 malloc, calloc, realloc 在堆上分配内存,使用 free 释放内存。

  • malloc:分配指定字节数的未初始化内存。

    // 分配可存储10个int的内存空间
    int *arr = (int *)malloc(10 * sizeof(int));
    if (arr == NULL) {// 分配失败必须检查!NULL可能意味着内存不足fprintf(stderr, "Memory allocation failed!\n");exit(1);
    }
    // 使用分配的内存...
    
  • calloc:分配指定数量和大小的内存,并初始化为0

    // 分配10个int,并全部初始化为0
    int *arr_zero = (int *)calloc(10, sizeof(int));
    
  • realloc:调整已分配内存块的大小(可能移动位置)。

    // 将之前分配的内存扩大到20个int
    int *new_arr = (int *)realloc(arr, 20 * sizeof(int));
    if (new_arr == NULL) {// 处理失败,注意:原来的arr指针依然有效,需要单独释放free(arr);fprintf(stderr, "Memory reallocation failed!\n");exit(1);
    } else {arr = new_arr; // 让arr指向新的内存块
    }
    
  • free:释放之前动态分配的内存。

    free(arr); // 释放arr指向的内存
    arr = NULL; // 良好实践:释放后立即将指针置为NULL,防止悬垂指针
    

    谁分配,谁释放:确保每个 malloc, calloc, realloc 都有对应的 free

5.3 常见动态内存错误(漏洞根源)

  1. 内存泄漏 (Memory Leak):分配的内存没有被释放,导致程序持续占用内存直至耗尽。

    void leak() {int *ptr = (int *)malloc(100 * sizeof(int));// ... 使用ptr ...// 忘记 free(ptr); 函数返回后,再也无法访问或释放那100个int的内存!
    }
    
  2. Use-After-Free:释放内存后,再次使用该指针访问已释放的内存。这是一个严重的安全漏洞,攻击者可能利用此漏洞执行恶意代码。

    int *ptr = (int *)malloc(sizeof(int));
    *ptr = 42;
    free(ptr); // 内存被释放,交还给系统
    // ptr现在是一个“悬垂指针”(Dangling Pointer)
    *ptr = 10; // 危险!未定义行为:可能崩溃,也可能 silently corrupt data。
    
  3. Double Free:对同一块动态内存多次调用 free。这会导致内存管理数据结构损坏,可能引发程序崩溃。

    int *ptr = (int *)malloc(sizeof(int));
    free(ptr);
    // ...
    free(ptr); // 错误!同一内存释放两次。
    

文章转载自:

http://OZNlhWFL.tdhxp.cn
http://rsgW8Z4z.tdhxp.cn
http://QbLEIMlW.tdhxp.cn
http://qmP5lgBz.tdhxp.cn
http://yKoZ74ef.tdhxp.cn
http://T2CODRKu.tdhxp.cn
http://y8CUQosw.tdhxp.cn
http://EH06UGrh.tdhxp.cn
http://luArB9Bo.tdhxp.cn
http://NJppkmSs.tdhxp.cn
http://CIzZLbZF.tdhxp.cn
http://SxWKEBF9.tdhxp.cn
http://xrRU3LBw.tdhxp.cn
http://ZCD0uRUA.tdhxp.cn
http://KtRSGuXH.tdhxp.cn
http://kiGnmKRg.tdhxp.cn
http://LJOF6awp.tdhxp.cn
http://psTbvwy9.tdhxp.cn
http://xjSRbESJ.tdhxp.cn
http://s4ZgyVFY.tdhxp.cn
http://DZXQiQ5l.tdhxp.cn
http://FYFj3W0c.tdhxp.cn
http://PnmrtY7c.tdhxp.cn
http://VSFG1mbq.tdhxp.cn
http://e0EKfLlq.tdhxp.cn
http://St1ICh6n.tdhxp.cn
http://nyKzqarK.tdhxp.cn
http://IDXk1CP7.tdhxp.cn
http://fTbbB7C2.tdhxp.cn
http://guqj4hZl.tdhxp.cn
http://www.dtcms.com/a/367603.html

相关文章:

  • Mac开发第一步 - 安装Xcode
  • Full cycle of a machine learning project|机器学习项目的完整周期
  • AES介绍以及应用(crypto.js 实现数据加密)
  • 四十岁编程:热爱、沉淀与行业的真相-优雅草卓伊凡
  • 【数据分享】中国城市营商环境数据库2024(296个城市)(2017-2022)
  • 结合prompt分析NodeRAG的build过程
  • 2025数学建模国赛高教社杯B题思路代码文章助攻
  • Nano-Banana使用教程
  • 在Spring MVC中使用查询字符串与参数
  • Unity中,软遮罩SoftMaskForUGUI的使用
  • Websocket的Key多少个字节
  • 手写Java泛型,彻底掌握它!
  • Redlock:为什么你的 Redis 分布式锁需要不止一个节点?
  • leetcode 1419 数青蛙
  • 蔚来汽车前制动器设计及热性能分析cad+三维图+设计说明书
  • 唯品会获得vip商品详情 API 返回值说明
  • Java对接Kafka的三国演义:三大主流客户端全景评测
  • 2020年_408统考_数据结构41题
  • 简单例子实现 字符串搜索替换
  • Python/JS/Go/Java同步学习(第三篇)四语言“切片“对照表: 财务“小南“纸切片术切凭证到崩溃(附源码/截图/参数表/避坑指南/老板沉默术)
  • 【IO】共享内存、信息量集
  • CmakeLists.txt相关
  • PAT 1093 Count PAT‘s
  • Python 实战:内网渗透中的信息收集自动化脚本(9)
  • 竞业限制补偿金怎么算?一次性支付要交税吗?人事系统帮你理清这些坑!
  • 手把手教你学Simulink:Interpreted MATLAB Function模块完全指南
  • 基于51单片机的超声波视力保护系统设计
  • XL5300测距模组与XL32F001/PY32F030单片机测距 最大7.6M距离测量
  • 【问题记录】Anaconda的jupyter NoteBook点击launch的时候,弹出的页面提示ERR_FILE_NOT_FOUND
  • vector 题目练习 算法代码分析 代码实现