多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE

技术文章

聚焦一线编程技术原理与实战踩坑,沉淀可复用的工程经验。

C 语言指针深度剖析:从原理到实战

C 语言指针深度剖析:从原理到实战

指针是 C 语言的灵魂,也是初学者最大的拦路虎。理解了指针,你就掌握了 C 语言直接操作内存的能力——这是它高效、强大的根源。本文从内存模型讲起,覆盖指针的本质、运算、函数指针与动态内存管理,并指出最常见的几个陷阱。

一、指针的本质:存储地址的变量

内存是一块连续的字节空间,每个字节有一个地址编号。指针变量本身也是一个变量,只不过它存储的是另一个变量的地址,而非数据本身。

#include <stdio.h>

int main(void) {
    int a = 42;        // 在栈上分配一个 int,存 42
    int *p = &a;       // p 是指针,存 a 的地址

    printf("a 的值    = %d\n", a);     // 42
    printf("a 的地址  = %p\n", &a);    // 0x7ffee...
    printf("p 的值    = %p\n", p);      // 同上
    printf("p 指向的值 = %d\n", *p);    // 42 —— 解引用
    *p = 100;          // 通过指针修改 a
    printf("a 现在    = %d\n", a);      // 100
    return 0;
}

一句话:变量存数据,指针存地址。& 取地址,* 解引用(取地址里的内容)。

二、指针类型与步长

指针有类型(int*、char*、double*),类型决定解引用时读几个字节,也决定指针加减时的步长。

int arr[] = {10, 20, 30};
int *p = arr;          // 数组名即首元素地址

printf("%d\n", *p);       // 10
printf("%d\n", *(p+1));   // 20 —— p+1 跨过 1 个 int(4 字节)
printf("%d\n", *(p+2));   // 30

// 不同类型步长不同
char *cp = (char*)arr;
printf("%d\n", *(cp+4));  // 跨过 4 个 char 才到 arr[1]

p+1 实际加的是 sizeof(指针所指类型) 字节,而不是 1。这就是指针"带类型"的意义。

三、指针与数组:一脉相承

数组名在大多数表达式中会"退化"为指向首元素的指针,所以指针和数组访问可以互换。

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;

// 这四种写法等价
printf("%d", arr[2]);
printf("%d", *(arr + 2));
printf("%d", p[2]);
printf("%d", *(p + 2));

四、函数指针:回调的基石

函数指针指向函数,可以动态调用不同函数,是回调、策略模式、事件驱动的底层支撑。

#include <stdio.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

// 函数指针类型:int (*)(int, int)
int calc(int (*op)(int, int), int a, int b) {
    return op(a, b);   // 通过指针调用函数
}

int main(void) {
    printf("%d\n", calc(add, 5, 3));  // 8
    printf("%d\n", calc(sub, 5, 3));  // 2
    return 0;
}

五、动态内存管理:堆上的指针

栈内存随函数返回自动释放,生命周期长的数据需用 malloc 在堆上分配,用完必须 free,否则内存泄漏。

#include <stdlib.h>
#include <stdio.h>

int main(void) {
    int n = 5;
    int *arr = malloc(n * sizeof(int));  // 申请 5 个 int
    if (arr == NULL) return 1;          // 必须检查返回值

    for (int i = 0; i < n; i++) arr[i] = i * 10;
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);  // 0 10 20 30 40

    free(arr);     // 释放内存
    arr = NULL;    // 置空,避免悬空指针
    return 0;
}

六、五大常见指针陷阱

  • 野指针:未初始化就解引用。声明时立即赋值或置 NULL。
  • 悬空指针:free 后仍使用。free 后立即置 NULL。
  • 内存泄漏:malloc 了没 free。配对使用,借助工具(valgrind)检查。
  • 越界访问:指针加减超出数组边界。务必校验索引范围。
  • 返回栈地址:函数内返回局部变量地址。改用 static、堆分配或参数传出。
// 反例:返回栈上局部变量地址 —— 经典错误
int* bad() {
    int x = 10;
    return &x;   // 函数返回后栈帧销毁,地址失效
}

// 正例:通过参数传出或使用堆
void good(int *out) { *out = 10; }

指针的价值在于"直接、高效"——它让你以字节级粒度操作内存,这是 C 语言能写操作系统、驱动、嵌入式底层的根本原因。掌握指针的关键不是背语法,而是建立清晰的内存模型:在脑中画出每个变量在内存中的位置、地址如何流转。多画图、多调试,指针终会成为你手中的利器。

返回列表