携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情
本系列主要是想从服务端开发者的视角梳理一下在开发中使用频率相对高一些的或者是比较容易踩坑的点,多多注意这些点可以让自己的水平得到质的飞跃:)
1 常量指针与指针常量
其实这里说的“常量”确切来说指的是带const关键字的变量,并非真正意义上的不能改变其值的量,只是从语义上做了一个君子协定。如果真的对带const关键字的量进行修改,那么根据C++标准,这是一种未定义行为。那么什么叫“未定义行为”呢?专业一点的话是指行为不可预测的计算机代码,也就是此行为具体会发生啥取决于各种编译器的具体实现,因编译器而异。举个小例子:
#include <cstdio> // 这里专门使用了C++风格的C标准库
int main() {
const volatile int i = 10; // 注意这里的volatile关键字哦
int* pi = (int*)(&i); // p是一个指向i地址的指针
*pi = 100; // 修改p指向的地址里面的值
printf( "*pi: %d\n",*pi);
printf( "i: %d\n",i);
printf( "pi: %p\n",pi); // %p是表示指针的格式控制符,打印出来的是16进制
printf( "&i: %p\n", &i);
return 0;
}
执行结果如下:
*pi: 100
i: 100
pi: 000000000063fe14
&i: 000000000063fe14
显然,pi其实就是一个地地道道的“常量指针”,就是指向常量的指针变量,这也符合中文语法基本原则,中文里面一般是把后面的作为主语。这段代码里,p就是通过指针的方式,修改了i的值。
这里如果不加volatile关键字,i的值将还是10。这又是为什么呢?这个时候去看一下汇编语句就能知道了,这是因为编译器在执行的时候,是从符号表里取的值、而不是从内存中取的值,这就叫做“常量折叠”现象。其实和我在之前文章中提到的缓存思想是一致的,因为有我们的const君子协定,所以直接从符号表里读会有性能提升且不会遇到上面修改const变量值问题。而这里的volatile关键字的作用就是指明了这个变量是“易变”的,让编译器不要轻易对它进行相关激进的优化,程序在执行时自然就还是乖乖回内存读取i的值了。
回到一开始说的常量指针和指针常量,其实很好区分这两者:如果const在*的右边,那么就是指针常量,其余都是常量指针。举个例子:
int* const p; // 指针常量
const int* p; // 常量指针