c语言indirection

99ANYc3cd6
预计阅读时长 17 分钟
位置: 首页 C语言 正文

核心概念:什么是指针?什么是解引用?

要理解解引用,首先要理解 指针

c语言indirection
(图片来源网络,侵删)

1 指针:一个内存地址的“名片”

在计算机中,所有的数据都存储在内存中,内存可以被看作是一条长长的街道,每个字节都有一个唯一的门牌号,这个门牌号就是 内存地址

  • 变量:是你给某个内存空间起的名字。int a = 10;,编译器会分配一块内存来存储整数 10,并给它命名为 a
  • 指针:是一个特殊的变量,它不直接存储数据,而是存储另一个变量的内存地址

可以把指针想象成一个名片,这张名片上写着另一个人的地址(内存地址),而不是那个人本身。

int a = 10; // 定义一个整型变量 a,值为 10
int *p = &a; // 定义一个整型指针 p,让它指向 a 的地址
// & 是取地址运算符
// * 在这里是声明指针的符号,表示 p 是一个指向整型的指针
  • &a:获取变量 a 的内存地址。
  • p:指针变量 p 的值,这个值就是 a 的地址。
  • *p:这就是 解引用 操作,它的意思是“根据 p 中存储的地址,找到那个内存位置,并访问里面的内容”。

2 解引用:通过地址找到真正的“房子”

解引用 就是使用指针变量中存储的地址,去访问该地址上实际存放的数据的过程,解引用操作符就是 。

int a = 10;
int *p = &a;
// 解引用 p
int b = *p; // *p 的值就是 10,b 的值也是 10
printf("a 的值: %d\n", a);       // 输出: a 的值: 10
printf("p 存储的地址: %p\n", p);  // 输出: p 存储的地址 (某个十六进制地址)
printf("通过 p 解引用得到的值: %d\n", *p); // 输出: 通过 p 解引用得到的值: 10

  • & (取地址运算符):作用于一个变量,获取其内存地址。
  • (解引用运算符):作用于一个指针,根据指针存储的地址访问其指向的值。

为什么需要解引用?(核心用途)

解引用是 C 语言指针功能的核心,它主要有以下几个重要用途:

1 修改指针指向的变量值

这是最常见、最重要的用途,通过指针,我们可以间接地修改另一个变量的值。

#include <stdio.h>
void modifyValue(int *ptr) {
    // 函数内部通过解引用来修改外部变量的值
    *ptr = 100; // 将 ptr 指向的内存地址中的数据改为 100
}
int main() {
    int num = 50;
    printf("修改前: num = %d\n", num); // 输出: 修改前: num = 50
    modifyValue(&num); // 将 num 的地址传递给函数
    printf("修改后: num = %d\n", num); // 输出: 修改后: num = 100
    return 0;
}

在这个例子中,modifyValue 函数通过解引用 ptr,成功修改了 main 函数中 num 的值,这就是 “通过指针传递参数” 的基本原理,是实现函数修改外部变量的关键。

2 动态内存分配

当你需要的数据大小在编译时无法确定时,就需要在程序运行时动态地申请内存。malloc, calloc, realloc 等函数返回的就是一个指向新分配内存的指针,你必须通过解引用来使用这块内存。

#include <stdio.h>
#include <stdlib.h> // 包含 malloc 的头文件
int main() {
    int size;
    printf("请输入数组的大小: ");
    scanf("%d", &size);
    // 1. 动态分配一块可以存放 size 个整数的内存
    int *array = (int *)malloc(size * sizeof(int));
    // 2. 检查分配是否成功
    if (array == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    // 3. 通过解引用来访问和赋值
    for (int i = 0; i < size; i++) {
        array[i] = i * 10; // array[i] 是 *(array + i) 的语法糖,本质也是解引用
    }
    // 4. 打印数组内容
    printf("动态数组的内容:\n");
    for (int i = 0; i < size; i++) {
        printf("array[%d] = %d\n", i, array[i]); // 同样是解引用
    }
    // 5. 释放内存
    free(array);
    return 0;
}

3 高效处理大型数据结构

当函数需要传递一个大型结构体或数组时,直接传递整个结构体或数组会产生巨大的 “拷贝开销”(复制大量数据非常耗时和耗内存),如果传递一个指向该数据结构的指针(通常只有4或8字节),效率会高得多。

// 一个巨大的结构体
struct BigData {
    char buffer[1024 * 1024]; // 1MB 的缓冲区
};
// 函数声明
void processBigData(struct BigData *data_ptr);
int main() {
    struct BigData my_data;
    // ... 对 my_data 进行一些初始化 ...
    // 传递指针,而不是整个结构体
    processBigData(&my_data);
    return 0;
}
void processBigData(struct BigData *data_ptr) {
    // 函数内部通过解引用来操作数据
    // (*data_ptr).buffer[0] = 'A'; // 这种写法也可以,但太繁琐
    data_ptr->buffer[0] = 'A'; // 使用 -> 运算符是更常见的写法
}

-> 运算符是 的简写,专门用于通过指针访问结构体/联合体的成员,它内部也包含了解引用的过程。


深入理解:指针的算术运算

解引用常常与指针的算术运算结合使用,这在处理数组和字符串时尤其重要。

当对一个指针进行加减运算时,它移动的 不是简单的字节,而是根据其指向的数据类型的大小进行移动。

  • p + 1:实际地址是 p 的地址 + sizeof(*p)
  • p - 1:实际地址是 p 的地址 - sizeof(*p)
int arr[3] = {10, 20, 30};
int *p = arr; // 数组名 arr 会“衰变”为其首元素的地址,p 指向 arr[0]
printf("p 指向的值: %d\n", *p);   // 输出: 10
printf("p 的地址: %p\n", (void*)p);
p++; // p 现在指向 arr[1],移动了 sizeof(int) 个字节(通常是4字节)
printf("p++ 后指向的值: %d\n", *p); // 输出: 20
printf("p++ 后的地址: %p\n", (void*)p);
// *(p + 1) 等价于 arr[2]
printf("*(p + 1) 的值: %d\n", *(p + 1)); // 输出: 30

危险的边缘:空指针和野指针

解引用是一把双刃剑,用不好会导致严重的程序错误。

1 空指针

一个值为 NULL 的指针,它不指向任何有效的内存地址。解引用空指针是未定义行为,几乎肯定会导致程序崩溃(段错误 Segmentation Fault)。

int *p = NULL; // p 是一个空指针
*p = 10;       // 错误!尝试向一个无效的内存地址写入数据,程序会崩溃

最佳实践:在解引用一个指针之前,总是检查它是否为 NULL

if (p != NULL) {
    *p = 10;
}

2 野指针

一个 “野指针” 是一个指向“未知”或“无效”内存地址的指针,它可能没有被初始化,或者指向的内存已经被释放了。

int *p; // p 是一个野指针,它的值是随机的,指向一个未知地址
*p = 5; // 极其危险!可能破坏程序数据或导致崩溃
int *q;
int a = 10;
q = &a;
printf("%d\n", *q); // q 是合法的
free(q); // 假设 q 是通过 malloc 分配的,现在释放了它指向的内存
*q = 20; // 错误!q 现在变成了一个野指针,它指向的内存已经不属于你的程序了

如何避免

  1. 永远不要初始化指针:指针在定义时必须被初始化(要么指向一个有效地址,要么设为 NULL)。
  2. 释放指针后立即置为 NULLfree(q); q = NULL;

概念 符号 作用 示例
指针 (声明时) 声明一个变量用于存储地址 int *p;
取地址 & 获取一个变量的内存地址 p = &a;
解引用 (使用时) 通过地址访问其指向的数据 int b = *p;
通过指针访问结构体成员 -> 解引用并访问成员 p->member

解引用是 C 语言中实现间接操作、动态内存和高效数据传递的基石。 它虽然强大,但也充满了风险,理解指针的每一个细节,特别是解引用的原理和潜在的危险,是掌握 C 语言的关键一步。

-- 展开阅读全文 --
头像
dede5.7 相关文章调用
« 上一篇 前天
织梦网站如何生成伪静态
下一篇 » 昨天

相关文章

取消
微信二维码
支付宝二维码

目录[+]