1.值替代
在使用宏#define进行常量替换时,仅仅只是文本替代,不进行类型检查。
#define BUFFSIZE 100
int buf[BUFFSIZE]
进行预处理
kernel@Ubuntu:~/Desktop/const$ cpp const.cpp const.i
kernel@Ubuntu:~/Desktop/const$ cat const.i
# 1 "const.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "const.cpp"
int buf[100];
BUFFSIZE只是一个名字,它在预处理的时候存在,在编译的时候不对该符号进行维护,也就是BUFFSIZE符号消失了。因此它不占用存储空间,仅仅在预处理期间就进行值替代,目的是为使用它的所有编译单元提供一个值。但是由于在编译的时候就是一个单纯的值,而且没有类型,如果出现不可思议的数,并不清楚这个数来自哪里。因此使用C++ const把值替代带到编译器阶段来消除这个问题,并不在预处理器阶段就进行值替代。
const in buffsize = 100;
这样编译的时候,编译器需要知道这个值在何处使用,同时编译器还可以执行常量折叠,把一个复杂的表达式进行简单化处理。在编译器看作为一个常量,把buffsize当作一个符号,并存放在符号表中,不给buffsize分配空间。
常量折叠
int main(void) {
const int buffsize = 100;
int i = buffsize;
int j = 99;
int k = j;
}<strong>
</strong>
对上面程序const.cpp进行编译链接生成可执行目标文件const,对const进行反汇编。
00000000004004ed <main>:
int main(void) {
4004ed: 55 push %rbp
4004ee: 48 89 e5 mov %rsp,%rbp
const int buffsize = 100;
4004f1: c7 45 f0 64 00 00 00 movl $0x64,-0x10(%rbp)
int i = buffsize;
4004f8: c7 45 f4 64 00 00 00 movl $0x64,-0xc(%rbp)
int j = 99;
4004ff: c7 45 f8 63 00 00 00 movl $0x63,-0x8(%rbp)
int k = j;
400506: 8b 45 f8 mov -0x8(%rbp),%eax
400509: 89 45 fc mov %eax,-0x4(%rbp)
}
const运行时,在对变量 i 进行赋值操作直接进行常量折叠,相当于直接把一个常量放入到一个i的栈空间。而非const的变量则需要进行从栈中取j的值,再放入寄存器eax中,从eax寄存器取出其值放入到i所在的栈空间。
2 头文件中的const
如果const为全局变量,则在编译后,将const修饰的符号的存放在.symtab的符号表中,但是const默认为内部链接,该符号为本地符号,不能被其他目标模块所引用,不会造成链接时的冲突。如需要引用加上extern进行引用。
不使用extern进行外部链接,buffsize的符号条目如下
6: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 _ZL8buffsize
可见buffsize是一个本地符号,不可以被其他模块引用。
使用extern进行外部链接
extern const int buffsize = 100;
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 4 buffsize
此时buffsize是一个全局符号,可以被其他模块引用。
C++ 编译器一般不为const成员分配存储空间,但是当使用了extern修饰const成员,其他模块会引用这个成员,所以会强制分配存储空间,但用extern声明const会阻止常量折叠。在对const成员进行取地址的时候,也会在分配空间。
3. C与C++ const的区别
(1)在C++中可以有如下定义:
const int buffsize = 100;
int buf[buffsize] = {0};
允许将一个定义为常量的变量当作数组的大小。C++中const在编译阶段被看作为一个常量,并不分配空间,并将其值存放在符号表中,在适当的时候进行常量折叠。
但是在C中,如果按照上述的定义会出现如下错误:
const.c:7:5: error: variable-sized object may not be initialized
int buf[buffsize] = {0};
因为在C中,const修饰的是一个不可改变的变量,会在运行时分配内存。所以编译器不知道编译时buffsize的值。
(2)C中可以有有如下定义:const int buffsize;
C编译器把它看做为一个声明,这样在C++的做法是不对的。在C中const为外部链接,声明上面的形式,如果外部有一个定义,则该模块可以引用。
而C++默认为内部链接,如果要进行如上声明,需要加上extern。
extern const int buffsize;
(3)C++中,编译器会把const看成一个符号,放入到符号表,通常不会给const分配空间,执行常量折叠。如果定义为全局变量也不会分配空间。而C中,一个const总是要占用内存空间。当const修饰的为全局符号且外部模块需要引用本地的const时,const默认为内部链接,需要加上extern,并且此时编译器强制为const分配空间,由于是const定义,可以进行常量折叠。对const进行&操作,也会为const分配空间。
#include <stdio.h>
int main(void) {
const int i = 100;
int * p = (int *)&i;
*p = 99;
printf("%p, %p, %d, %d\n", &i, p, i, *p);
}
分别使用gcc和g++进行编译。
kernel@Ubuntu:~/Desktop/const$ g++ -g const.c -o const
kernel@Ubuntu:~/Desktop/const$ ./const
0x7ffd5b36fa74, 0x7ffd5b36fa74, 100, 99
kernel@Ubuntu:~/Desktop/const$ gcc -g const.c -o const
kernel@Ubuntu:~/Desktop/const$ ./const
0x7ffd74dd4154, 0x7ffd74dd4154, 99, 99
当使用g++编译的时候,虽然p和i的地址相同,但是i和*p的值不一样。因为在编译阶段,没有对const分配空间,对const进行了常量折叠,将i用100来代替,也就是说i自身变为了100,并不是其空间存放了100。虽然用&i分配了一个内存空间,使用指针对其指向的内容进行了赋值。但是i的值不会变,内存空间里的值会改变。
因为C总是要为const分配一个空间,所以指针p指向的空间,就是存储i值的空间,因此在指针更改其指向空间的值的时候,i的值也被改变。
注:在用const int *对int * p进行赋值时,要将const int *强制转化为int *,否则c++编译会报错,而c会报警告。
kernel@Ubuntu:~/Desktop/const$ gcc -g const.c -o const
const.c: In function ‘main’:
const.c:7:15: warning: initialization discards ‘const’ qualifier from pointer target type [enabled by default]
int * p = &i;
kernel@Ubuntu:~/Desktop/const$ g++ -g const.c -o const
const.c: In function ‘int main()’:
const.c:7:16: error: invalid conversion from ‘const int*’ to ‘int*’ [-fpermissive]
int * p = &i;