解码C语言指针

指针的定义与本质

指针是什么?

指针是一种 存储变量内存地址 的特殊变量。所有数据存储在内存中,每个内存单元都有唯一地址(编号),指针通过记录地址实现对数据的间接访问。

指针的核心作用
  • 直接操作内存:动态内存分配、硬件编程等。
  • 提高效率:传递大对象时避免复制(如结构体)。
  • 灵活数据结构:实现链表、树、图等动态结构。

指针基本语法

声明指针
数据类型 *指针变量名;// *表示这是个指针变量

示例

int *p;// p指向int类型变量
char *str;// str指向char类型变量
指针初始化
  • 取地址操作符 &:获取变量的内存地址。

    int num = 10;
    int *p = #// p指向num的地址
    
  • 直接赋值(需确保地址合法):

    int *p = (int *)0x00007FFF1234;// 避免直接操作未知地址
    

指针的解引用

  • 解引用操作符 * :通过指针访问或修改目标地址的数据。

    int num = 10;
    int *p = #
    
    printf("num的值 = %d\n", *p);// 输出10(访问值)
    *p = 20;// 修改num为20
    

指针的算术运算

指针的加减操作以 数据类型大小 为单位进行偏移。

指针与整数运算
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;// p指向arr[0]

p++;// p指向arr[1](地址+4字节,假设int为4字节)
printf("%d\n", *p);// 输出2

p += 3;// p指向arr[4]
printf("%d\n", *p);// 输出5
指针之间的减法

计算两个指针之间的元素距离(同类型指针):

int *p1 = &arr[0];
int *p2 = &arr[4];
printf("距离 = %ld\n", p2 - p1);// 输出4(间隔4个元素)

多级指针

  • 二级指针:指向指针的指针。
int num = 10;
int *p = #//数据类型 *指针变量名;
int **pp = &p;// pp指向p的地址
printf("%d\n", **pp);// 通过二级指针访问num的值 → 输出10
遍历二维数组(数组指针)
#include <stdio.h>
int main() {
    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*p)[3] = matrix;// 指向第一行的地址
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", p[i][j]);// 等价于 *(*(p + i) + j)
        }
        printf("\n");
    }
    return 0;
}

指针与数组与字符串

数组名的本质

数组名是数组首元素的地址常量(不可修改)。

int arr[3] = {10, 20, 30};
int *p = arr;// p指向arr[0]
printf("%d\n", *(p + 1));// 右移一个int单位 → 输出20
指针遍历数组
for (int *ptr = arr; ptr < arr + 3; ptr++) {
    printf("%d ", *ptr);// 输出10 20 30
}
遍历字符串
char *p = "Hello";
while (*p != '\0') {
    printf("%c ", *p);// 输出 H e l l o
    p++;
}
字符串数组(指针数组)
char *fruits[] = {"Apple", "Banana", "Cherry"};// 指针数组
for (int i = 0; i < 3; i++) {
    printf("%s\n", fruits[i]);// 输出各水果名称
}
printf("%s\n",*(fruits + 1));//输出Banana,数组的第二个元素
printf("%c\n",*(*(fruits + 1) + 1));//输出a,Banana的第二个字符

指针与函数

传递指针到函数
// 修改外部变量值
void increment(int *x) {
    (*x)++;
}

int main() {
    int a = 5;
    increment(&a);// a变为6
    return 0;
}
返回指针的函数(指针函数)
/* 返回数组最大元素的地址,只能返回静态变量的地址、动态开辟(malloc)的地址,全局变量
的地址等,不能返回局部变量的地址(否则程序会崩溃)
*/
int* find_max(int *arr, int size) {
    int *max = arr;  // max指向数组首元素
    for (int *p = arr; p < arr + size; p++) {
        if (*p > *max) max = p;  // 找到更大元素时,让max指向该元素
    }
    return max;  // 返回指向数组中最大元素的指针
}
int* find_max(int *arr, int size) {
    static int max = *arr;  // 静态变量,仅初始化一次
    for (int *p = arr; p < arr + size; p++) {
        if (*p > max) max = *p;  // 更新max为更大的元素值
    }
    return &max;  // 返回静态变量max的地址
}
函数指针

指向函数的指针,用于回调机制:

int add(int a, int b) { return a + b; }
int (*func_p)(int, int) = add;// 声明函数指针
printf("%d\n", func_p(3, 4));// 输出7

void * 指针

  • 通用指针,可指向任意类型数据。
  • 使用前需强制类型转换。
void *ptr;
int x = 10;
ptr = (void *)&x;// 合法
printf("%d\n", *(int *)ptr);// 需转换为int*

const型指针

核心规则:const 的位置决定保护的对象

const*左边,保护的是指针所指向的数据*p 不可变)。

const*右边,保护的是指针本身p 不可变)。


指向常量的指针(Pointer to Constant)

格式: const 类型 *ptr类型 const *ptr

含义: 指针 ptr 可以指向不同的地址,但不能通过 ptr 来修改它所指向的数据。

示例:

int a = 10, b = 20;

// 指向常量的指针
const int *ptr1 = &a;// ptr1 指向一个const int
int const *ptr2 = &a;// 等价写法
// *ptr1 = 15;  // 错误!不能通过ptr1修改数据
a = 15;// 合法,a本身不是const

ptr1 = &b;// 合法,ptr1可以指向其他地址
// *ptr1 = 25;  // 错误!仍然不能通过ptr1修改数据
指针常量(Constant Pointer)

格式: 类型 * const ptr

含义: 指针 ptr 自身是常量,一旦初始化就不能再指向其他地址,但可以通过 ptr 修改它所指向的数据。

示例:

int x = 10, y = 20;

// 指针常量
int * const ptr = &x;// ptr是常量指针
*ptr = 15;// 合法,可以修改指向的数据
// ptr = &y;    // 错误!ptr本身是常量,不能改变指向
指向常量的指针常量(Constant Pointer to Constant)

格式: const 类型 * const ptr

含义: 既不能修改指针 ptr 的指向,也不能通过 ptr 修改所指向的数据。

示例:

const int z = 30;
int w = 40;

// 指向常量的指针常量
const int * const ptr = &z;

// *ptr = 35;    // 错误!不能修改数据
// ptr = &w;     // 错误!不能修改指针指向
int read = *ptr;// 合法,只能读取
总结表格(带变量名)
声明格式指针(ptr)是否可修改*数据(ptr)是否可修改说明
int *ptr普通指针
const int *ptr指向常量的指针
int * const ptr指针常量
const int * const ptr指向常量的指针常量

野指针

野指针是指指向无效内存地址的指针。这些指针通常指向已经被释放或未分配的内存区域。对野指针进行操作会导致未定义行为

主要成因
成因一:指针未初始化
int *ptr;// 未初始化,值是随机的垃圾值
*ptr = 10;// 危险!向随机内存地址写入数据
printf("%d", *ptr);// 可能导致程序崩溃
成因二:malloc后未检查或释放后继续使用
int *ptr = (int*)malloc(sizeof(int));
if (ptr != NULL) {
    *ptr = 100;
}
free(ptr);// 释放内存
// ptr现在成为野指针
//*ptr = 200;// 危险!访问已释放的内存
printf("%d", *ptr);// 未定义行为
成因三:指向局部变量(函数返回后)
int* createWildPointer() {
    int local = 42;
    return &local;// 返回局部变量的地址
}

int main() {
    int *ptr = createWildPointer();// ptr是野指针
    printf("%d", *ptr);// 危险!local已销毁
    return 0;
}
成因四:指针越界访问
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;

for (int i = 0; i < 10; i++) {
	// 越界访问
	printf("%d ", *(ptr + i));
	// 后5次是野指针访问
}
成因五:指针运算错误
int *ptr = (int*)malloc(5 * sizeof(int));
// ... 使用ptr
free(ptr);

int *ptr2 = ptr + 2;// ptr2也是野指针
野指针的危害

程序崩溃:段错误(Segmentation Fault)
数据损坏:意外修改其他有效数据
安全漏洞:可能被利用进行攻击
难以调试:错误表现随机,难以追踪

避免野指针的方法
方法一:总是初始化指针
int *ptr = NULL;// 初始化为NULL
int *ptr2 = (int*)malloc(sizeof(int));// 或初始化为有效地址
if (ptr2 != NULL) {
    *ptr2 = 10;
}
方法二:释放后立即置NULL
int *ptr = (int*)malloc(sizeof(int));
if (ptr != NULL) {
    *ptr = 100;
    printf("%d\n", *ptr);
}

free(ptr);// 释放内存
ptr = NULL;// 立即置NULL
// 安全检查
if (ptr != NULL) {
    printf("%d\n", *ptr);// 不会执行
} 
else {
    printf("指针已释放\n");
}
方法三:避免返回局部变量地址
// 错误做法
int* badFunction() {
    int local = 5;
    return &local;
}

// 正确做法1:使用静态变量
int* goodFunction1() {
    static int staticVar = 5;// 静态变量生命周期延长
    return &staticVar;
}

// 正确做法2:使用动态内存
int* goodFunction2() {
    int *dynamicVar = (int*)malloc(sizeof(int));
    if (dynamicVar != NULL) {
        *dynamicVar = 5;
    }
    return dynamicVar;
}

// 正确做法3:通过参数返回
void goodFunction3(int **result) {
    *result = (int*)malloc(sizeof(int));
    if (*result != NULL) {
        **result = 5;
    }
}
方法四:使用函数返回值检查
int *createArray(int size) {
    if (size <= 0) {
        return NULL;// 返回NULL而不是野指针
    }
    return (int*)malloc(size * sizeof(int));
}

int main() {
    int *arr = createArray(5);
    if (arr == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

	// 使用arr...
	free(arr);
	arr = NULL;

    return 0;
}
方法五:封装内存管理函数
// 安全的malloc封装
void* safeMalloc(size_t size) {
    void *ptr = malloc(size);
    if (ptr == NULL) {
        fprintf(stderr, "内存分配失败\n");
        exit(EXIT_FAILURE);
    }
    return ptr;
}

// 安全的free封装
void safeFree(void **ptr) {
    if (ptr != NULL && *ptr != NULL) {
        free(*ptr);
        *ptr = NULL;// 自动置NULL
	}
}

int main() {
    int *arr = (int*)safeMalloc(5 * sizeof(int));

	// 使用arr...

    safeFree((void**)&arr);// 自动置NULL
    // 现在arr为NULL,不会成为野指针

    return 0;
}
检测野指针的工具
使用Valgrind(Linux)
gcc -g program.c -o program
valgrind --leak-check=full ./program

动态内存分配

使用指针管理堆内存(需引入 <stdlib.h>):

int *arr = (int *)malloc(5 * sizeof(int));// 分配5个int的空间
if (arr != NULL) {
    arr[0] = 100;
	// 使用完毕后释放内存
	free(arr);
    arr = NULL;// 避免野指针
}

指针的注意事项与应用场合

问题说明规避方法
野指针指向未知内存的指针初始化时置空,释放后置空
空指针解引用NULL 指针进行操作导致崩溃使用前检查指针是否为 NULL
内存泄漏未释放动态分配的内存确保每个 malloc 对应一个 free
指针类型不匹配操作不同类型指针导致数据错误避免强制类型转换的误用
  • 当函数需要传递一个数组时,指针代替数组
  • 当函数需要返回一个地址(例如函数需要返回数组)的时候,需要使用指针
    实现。
  • 当函数需要改变实参的时候,需要使用指针实现。-----形参改变实参

代码示例

指针操作基本流程
#include <stdio.h>
int main() {
    int a = 10;
    int *p = &a;

    printf("a的地址 = %p\n", (void *)p);// 输出地址(如0x7ffd...)
    printf("a的值 = %d\n", *p);// 输出10

    *p = 20;
    printf("修改后a的值 = %d\n", a);// 输出20
    return 0;
}
动态数组操作
#include <stdio.h>
#include <stdlib.h>
int main() {
    int size = 5;
    int *arr = (int *)malloc(size * sizeof(int));

    if (arr == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }

    for (int i = 0; i < size; i++) {
        arr[i] = i * 10;
    }

    for (int *p = arr; p < arr + size; p++) {
        printf("%d ", *p);// 输出0 10 20 30 40
    }

    free(arr);
    arr = NULL;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值