【C/C++】 从内存模型到访问权限:一文读懂常量指针与指针常量的本质区别
1. 指针基础与内存模型
在C/C++的世界里,指针就像是一把双刃剑——用得好能让你游刃有余,用不好则可能伤及自身。要理解常量指针和指针常量的区别,我们得先从内存模型这个底层视角说起。
想象你的电脑内存是一个超大型的储物柜,每个柜子都有唯一的编号(内存地址),里面可以存放各种物品(数据)。当我们声明一个变量时,比如int a = 10;,系统会分配一个柜子(假设编号0x1100)来存放这个整数。指针本质上就是记录这些柜子编号的便利贴,它自己也有个小柜子存放地址值。
在32位系统中,这个"便利贴"占4个字节(因为地址用32位表示);64位系统中则占8个字节。我经常用这个类比:指针就像酒店的前台登记簿,记录着每个客人(数据)的房间号,而解引用操作(*p)就是根据登记簿找到客人房间的过程。
int a = 10; // 数据存放在0x1100柜子 int *p = &a; // p的值是0x11002. const关键字的双重身份
const这个修饰符在C++中扮演着双重角色,就像瑞士军刀的不同功能:
- 常量修饰:
const int x = 5;表示x的值不可修改 - 指针修饰:当const遇到指针时,它的位置决定了限制对象
我有个简单的记忆口诀:"const靠谁近,谁就不可变"。比如:
const int *p:const修饰*p(内容不可变)int * const p:const修饰p(指针本身不可变)
在实际项目中,我经常用const来防止意外修改。比如函数参数用const char*,就像给参数加了防篡改封条,既安全又表明设计意图。
3. 指针常量:锁定的导航仪
指针常量(int * const p)的特点是——指针本身的值(地址)不可变,但可以通过指针修改指向的内容。这就像你手机上的导航App被锁定了一个目的地,不能更改目的地地址,但可以修改目的地店铺的装修。
int a = 10, b = 20; int * const p = &a; // 初始化时必须赋值 *p = 30; // 合法:可以装修店铺 // p = &b; // 非法:不能更改导航目的地在嵌入式开发中,我常用指针常量来访问固定的硬件寄存器地址。比如操作STM32的GPIO端口时,寄存器地址是芯片设计固定的,但需要读写寄存器值:
#define GPIOA ((volatile uint32_t * const)0x40020000) *GPIOA = 0xFFFF; // 可以修改寄存器值 // GPIOA = ...; // 禁止修改指针地址4. 常量指针:防篡改的保险箱
常量指针(const int *p)则相反——可以改变指针指向的地址,但不能通过指针修改指向的内容。这就像银行保险箱的钥匙:你可以换不同的保险箱开,但不能修改保险箱里的物品。
int a = 10, b = 20; const int *p = &a; p = &b; // 合法:可以换保险箱 // *p = 30; // 非法:不能修改内容在开发图像处理库时,我常用常量指针来传递图像数据,确保处理函数不会意外修改原始图像:
void processImage(const uint8_t *pixels, int width) { // 可以读取pixels数据 // 但不能通过pixels修改数据 }5. 指向常量的指针常量:双重保险
当const同时出现在类型前和*后时(const int * const p),就形成了"双重锁定"——既不能修改指针指向的地址,也不能通过指针修改内容。这就像把珍贵文物锁进防弹玻璃展柜,既不能移动展柜,也不能触碰文物。
int a = 10; const int * const p = &a; // *p = 20; // 非法 // p = &b; // 非法在开发安全敏感的系统时,比如支付模块,我会用这种双重保护来确保关键数据的安全。有一次我设计加密模块时,就用这种指针来保护密钥数据,既防止指针被篡改,也防止密钥被意外修改。
6. 从编译器视角看本质
理解这些区别的关键在于明白编译器如何解读const修饰。编译器会检查:
- 对指针变量本身的赋值(p = x)
- 通过指针的解引用操作(*p = x)
对于int * const p,编译器会阻止对p的赋值,但允许*p操作。而对于const int *p,则相反。我曾用Compiler Explorer工具查看过生成的汇编代码,发现编译器会对违规操作直接报错,根本不会生成对应指令。
7. 实战中的经典坑点
在多年开发中,我踩过不少关于指针的坑:
- 类型不匹配:
const int a = 10; int *p = &a;会导致编译错误,需要用const int *p - 多级指针:
const int **pp和int * const *pp的区别更复杂 - 函数返回:返回指向局部变量的指针是未定义行为
有个特别隐蔽的bug让我记忆犹新:在跨平台代码中,我误用了指针常量指向只读内存区域,在Windows上运行正常,但在嵌入式设备上硬故障。后来用const正确修饰后才解决问题。
8. 现代C++的替代方案
虽然理解这些概念很重要,但在现代C++中,我们有了更安全的替代方案:
- 引用(
const T&)比指针更安全 std::unique_ptr和std::shared_ptr管理生命周期std::span或gsl::span表示视图范围
不过在内核开发或嵌入式领域,原始指针依然无可替代。最近我在开发一个高频交易系统时,就不得不用指针常量来确保极致的性能。
