C++ 常量指针与指针常量深度解析:const 修饰符的内存安全实践

概述

常量指针和指针常量的差异

示例

  • 常量指针:指向常量的指针,指针指向可以更改,但是指向的值因为是个常量,所以不能更改

    c++ 复制
    int a = 10;
    int b = 20;
    const int *p = &a;
    p = &b; // 正确,改变了指向
    *p = b; // 错误,不能改变值,值是个常量
    

    注意:常量指针做函数参数,可以防止指针指向值在函数内部被修改。

  • 指针常量:指针的值是个常量,所以指向不能更改,但是指向的值可以更改

    c++ 复制
    int a = 10;
    int b = 20;
    int * const p = &a;
    p = &b; // 
    错误,不能改变指针指向
    *p = b; // 正确,可以改变指针指向的值
    

    注意:指针常量做参数,主要用于 out params(函数通过参数向外部传递值),防止指针指向改变

  • const 既修饰指针,又修饰常量:指针的指向,指针的值都不可以更改

    c++ 复制
    int a = 10;
    int b = 20;
    const int * const p = &a;
    p = &b; // 错误,不能改指针的指向
    *p = b; // 错误,不能改变指向的值
    

运算

指针的运算不同普通的数值运算,指针运算是与指针的类型息息相关。例如:

c 复制
char *p; *(p + 1) == p[1]; // p + 1 等于指针值 + 1 x sizeof(char)
int *p; *(p + 1) == p[1]; // p + 1 等于指针 p 的值 + 1 x szieof(int)
long long *p; // p + 1 等于 p + 1 x (sizeof(long long)),变化了 8 个字节

p[1] 相当于 *(p + 1),p[n] 相当于 *(p + n),实质其实是相当于对指针的值的一个运算,这个运算涉及到指针的类型。void *类型之所以无法解引用,是因为他没有类型,无法对指针指向的地址处进行取值(不知道类型,解引用后不知道数据的宽度)

多级指针

二级指针 char **p,相当于指针p 指向指针p1,指针p1指向字符的地址。对指针的运算,相当于对目的地址的一个变化,所以二级指针,对 p 进行操作后,将无法再寻得最终的字符。p[0] 相当于 *p

多维数组的内存分配是一块连续的内存地址,否则将无法进行指针运算。

例如:A[2][3][4]A 的类型是 int (*)[3][4]A[0]的类型是int (*)[4]A[0][0]的类型是int *A + 1等于移动了int(*)[3][4]的距离,相当于 A地址 + sizeof(int) x 3 x 4

当一个多级指针指向一个连续分配内存的地址的时候,才能想数组一样进行各级的指针运算。否则,只能进行依次的解引用操作,因为中间的指针进行运算的时候,就会导致无法寻到最后指向的数据。

与汇编的关系

assembly 复制
jmp dword ptr [0x12345678] // 跳转到 0x12345678 内存地址中存储的双字的一个地址。

其中 [地址]这种形式,相当于对一个指针地址进行解引用,但是指针解引用需要指针类型(也就是解引用后取数据的宽度),这个就是中括号前面的关键字dword ptr,表示是双字。