// 2020.06.25更新
最近看了《C++语言的设计与演化》,在3.8节中说明了const关键字的来龙去脉。
// 以下为原文
const关键字在C和C++中并非相同的含义。从字面含义上,const很容易让人认为它所限定的量是一个常量Constant,这样的理解在C++中是正确的,而在C语言中则是错误的。
在C语言中,const的作用,更准确地说是将变量限定为read-only。
Let’s look at what is meant when const is used. It’s really quite simple: const means that something is not modifiable, so a data object that is declared with const as a part of its type specification must not be assigned to in any way during the run of a program. It is very likely that the definition of the object will contain an initializer (otherwise, since you can’t assign to it, how would it ever get a value?), but this is not always the case. For example, if you were accessing a hardware port at a fixed memory address and promised only to read from it, then it would be declared to be const but not initialized.
——https://publications.gbdirect.co.uk//c_book/chapter8/const_and_volatile.html
仔细想想,read-only其实并不意味着not change。
结合volatile来看就清晰了,用C Primer Plus中的栗子来说:
可以同时用const和volatile限定一个值。例如,通常用const把硬件时钟设置为程序不能更改的变量,但是可以通过代理改变,这时用 volatile。
至于在C++中,const的作用就就类似于C语言的#define了,由编译器进行值替代,这是Thinking in C++ 第8章中的解释:
当定义一个const时,必须赋一个值给它,除非用extern作出了清楚的说明。
通常C++编译器并不为const创建存储空间,相反它把这个定义保存在它的符号表里。但是,上面的extern强制进行了存储空间分配(另外还有一些情况,如取一个const的地址,也要进行存储空间分配),由于extern意味着使用外部连接,因此必须分配存储空间,这也就是说有几个不同的编译单元应当能够引用它,所以它必须有存储空间。
通常情况下,当extern不是定义的一部分时,不会分配存储空间。如果使用const,那么编译时会进行常量折叠。
——但是对于“不为const创建存储空间”的说法,目前还不太理解。。。
来看看下面这段代码:
int main(void)
{
int iVal;
const int constVal = 5;
int *ptr = (int *)&constVal; //强制类型转换
*ptr = 3;
iVal = constVal;
return 0;
}
当作为C语言时得到的汇编代码如下,给iVal
赋值时实际上是先从constVal
所在的内存中取值,再将取得的值赋给iVal
,由于在此之前通过ptr
对constVal
内存进行了修改,因此iVal
值为3,而非constVal
的初始值5。
main.c:
2 {
0x0000000100401080 <+0>: push %rbp
0x0000000100401081 <+1>: mov %rsp,%rbp
0x0000000100401084 <+4>: sub $0x30,%rsp
0x0000000100401088 <+8>: callq 0x1004010d0 <__main>
3 int iVal;
4 const int constVal = 5;
0x000000010040108d <+13>: movl $0x5,-0x10(%rbp)
5
6 int *ptr = (int *)&constVal;
0x0000000100401094 <+20>: lea -0x10(%rbp),%rax
0x0000000100401098 <+24>: mov %rax,-0x8(%rbp)
7 *ptr = 3;
0x000000010040109c <+28>: mov -0x8(%rbp),%rax
0x00000001004010a0 <+32>: movl $0x3,(%rax)
8
9 iVal = constVal; //iVal = 3;
0x00000001004010a6 <+38>: mov -0x10(%rbp),%eax
0x00000001004010a9 <+41>: mov %eax,-0xc(%rbp)
10
11 return 0;
=> 0x00000001004010ac <+44>: mov $0x0,%eax
12 }
0x00000001004010b1 <+49>: add $0x30,%rsp
0x00000001004010b5 <+53>: pop %rbp
0x00000001004010b6 <+54>: retq
然而作为C++时则得到不同的结果,在给iVal
赋值时是直接将立即数0x5赋给iVal
,也就是constVal
的初始值;虽然此时constVal
已经被修改为3了,但不会影响到iVal
。
main.cpp:
2 {
0x0000000100401080 <+0>: push %rbp
0x0000000100401081 <+1>: mov %rsp,%rbp
0x0000000100401084 <+4>: sub $0x30,%rsp
0x0000000100401088 <+8>: callq 0x1004010d0 <__main>
3 int iVal;
4 const int constVal = 5;
0x000000010040108d <+13>: movl $0x5,-0x10(%rbp)
5
6 int *ptr = (int *)&constVal;
0x0000000100401094 <+20>: lea -0x10(%rbp),%rax
0x0000000100401098 <+24>: mov %rax,-0x8(%rbp)
7 *ptr = 3;
0x000000010040109c <+28>: mov -0x8(%rbp),%rax
0x00000001004010a0 <+32>: movl $0x3,(%rax)
8
9 iVal = constVal; //iVal = 5, constVal = 3;
0x00000001004010a6 <+38>: movl $0x5,-0xc(%rbp)
10
11 return 0;
=> 0x00000001004010ad <+45>: mov $0x0,%eax
12 }
0x00000001004010b2 <+50>: add $0x30,%rsp
0x00000001004010b6 <+54>: pop %rbp
0x00000001004010b7 <+55>: retq
如果把constVal
定义为全局变量,那么无论是在C还是C++中,实际运行的时候都会出现 Segmentation fault。以C为例,使用nm
命令看一下符号表,发现——
0000000100403000 R constVal
其中R
说明该符号位于只读数据区,尝试修改只读数据的值显然会导致错误了。
关于C++中const的用法,还可以参考Const, constexpr, and symbolic constants,其中对runtime constants(initialization values can only be resolved at runtime ,when your program is running)和compile-time constants(initialization values can be resolved at compile-time,when your program is compiling)也进行了解释,上述栗子中的constVal
是一个compile-time constants,因此C++编译器才能对其进行值替换。