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

详细介绍:【C语言】第四课 指针与内存管理

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

1.1 什么是指针?

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

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);
// 地址值比之前增加了4
p += 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://www.wxhsa.cn/company.asp?id=3601

相关文章:

  • 知识点错题整理
  • 202311_陇剑杯预赛_tcpdump
  • Linux学习记录(六):添加/删除用户
  • python 链式调用 合并 __setattr__ __getattribute__ in nested object()
  • 分享一个稳定好用的免费云服务——阿贝云体验
  • 年化439%,回撤7%,卡玛比率62.5,附本地运行的完整策略python代码 - 详解
  • 接口测试---PyMysql
  • My First Blog
  • 设置基础软件仓库时出错
  • linux c应用性能与内存泄露问题排查工具
  • 深入解析:AI-调查研究-66-机器人 机械臂 软件算法体系:轨迹规划视觉定位力控策略
  • VS Code快捷键
  • API安全厂商综合推荐:2025年权威视角下的主流厂商评估与选型指南
  • 基于FPGA的8PSK+帧同步系统verilog开发,包含testbench,高斯信道,误码统计,可设置SNR
  • 去去就来
  • 使用 CUDA 12.9 编译 PyTorch 2.4.0
  • 豆包生成C#即梦API HTTP调用实例代码
  • 解析几何笔记
  • 基于SOA海鸥优化算法的PID控制器最优控制参数计算matlab仿真
  • 详细介绍:boost::circular_buffer的使用方法简介
  • 基于禁忌搜索算法的TSP问题最优路径搜索matlab仿真
  • PDD9.14 笔试 - 浪矢
  • 增肌,减脂,变瘦的联系和区别
  • (eval):1: _python-argcomplete: function definition file not found
  • 详细介绍:【Spring Boot 报错已解决】Web server failed to start. Port 8080 was already in use.
  • Nordic Neuton.AI 技术优势;
  • channel Sounding 工作流程
  • 基于Zhang-Suen算法的图像细化处理FPGA实现,包含testbench和matlab验证程序
  • channel Sounding RTT和PBR 属性总结
  • 二分查找方法