常量概念:为了使程序员能够在变和不变之间画一条界限,这在C++程序设计中提供了安全性和可控性。
const的最初动机是取代预处理器#define来进行值替代。
宏(#define)
预编译器可以不受限制的建立宏并用它来代替值。预编译器只做文本替代,它没有类型检查概念,也没有类型检查功能。
宏定义只在预处理期间存在,因此它不占用存储空间且能放在一个头文件里,目的是在使用它的所有编译单元提供一个值。
const
const默认为内部连接,也就是说,const仅在const被定义过的文件里才是可见的,而在连接时不能被其他编译单元看到。当定义一个const时,必须赋一个值给它,除非用extern作出了清楚的说明。
eg:extern const int bufsize;
通常情况下,当extern不是定义的一部分,不会分配存储空间。如果用const,那么编译时会进行常量折叠。
常量折叠:编译器在编译时可以通过必要的计算吧一个复杂的常量表达式通过缩减简单化。
为const创建储存空间:(1):复杂的结构;(2):强制声明为extern的符号常量;(3):取符号常量的地址等操作
由于extern意味着使用外部连接,因此必须分配储存空间,这也就是说有几个不同的编译单元应当能够引用它,所以它必须有存储空间。
以上主要参考《C++编程思想》
----------------------------------------------------------------------------------------------------------------------------------------------
通过验证发现了一些问题
#include "stdafx.h"
#include<iostream>
using namespace std;
int main()
{
int dd = 2;
const int n = 9;
int *p = (int*)&n;
*p = 1;
cout << &n << " " << n << endl;
cout<< p << " " << *p << endl;
cout << *(&n) <<" "<< *(&n + 1 - 1) << endl;
int tp = 5;
cout << *(&tp) << " " << *(&tp + 1 - 1) << endl;
return 0;
}
结果:
与预期不一致
1.地址相同,内容不一样
2.通过指针修改const n
3.*(&n)与*(&n+1-1)值为何不一样
注:const int n 不是编译期间的const,推测n不是储存在常量储存区,所以常量n值是可以改变的。若extern const int n = 9;则n定义在常量储存区n值是不能改变的,见:下面例2。(暂时没有看到有明确说明的资料)
看一下汇编
int main()
{
001E2500 push ebp
001E2501 mov ebp,esp
001E2503 sub esp,0F4h
001E2509 push ebx
001E250A push esi
001E250B push edi
001E250C lea edi,[ebp-0F4h]
001E2512 mov ecx,3Dh
001E2517 mov eax,0CCCCCCCCh
001E251C rep stos dword ptr es:[edi]
001E251E mov eax,dword ptr ds:[001EB008h]
001E2523 xor eax,ebp
001E2525 mov dword ptr [ebp-4],eax
int dd = 2;
001E2528 mov dword ptr [dd],2
const int n = 9;
001E252F mov dword ptr [n],9
int *p = (int*)&n;
001E2536 lea eax,[n]
001E2539 mov dword ptr [p],eax
*p = 1;
001E253C mov eax,dword ptr [p]
*p = 1;
001E253F mov dword ptr [eax],1
cout << n << endl;
001E2545 mov esi,esp
001E2547 push 1E1082h
001E254C mov edi,esp
001E254E push 9 //常量折叠
001E2550 mov ecx,dword ptr ds:[1EC098h]
001E2556 call dword ptr ds:[1EC0ECh]
001E255C cmp edi,esp
001E255E call __RTC_CheckEsp (01E1159h)
001E2563 mov ecx,eax
001E2565 call dword ptr ds:[1EC0ACh]
001E256B cmp esi,esp
001E256D call __RTC_CheckEsp (01E1159h)
cout << *p << endl;
001E2572 mov esi,esp
001E2574 push 1E1082h
001E2579 mov edi,esp
001E257B mov eax,dword ptr [p]
001E257E mov ecx,dword ptr [eax]
001E2580 push ecx
001E2581 mov ecx,dword ptr ds:[1EC098h]
001E2587 call dword ptr ds:[1EC0ECh]
001E258D cmp edi,esp
001E258F call __RTC_CheckEsp (01E1159h)
001E2594 mov ecx,eax
001E2596 call dword ptr ds:[1EC0ACh]
001E259C cmp esi,esp
001E259E call __RTC_CheckEsp (01E1159h)
cout << &n << endl;
001E25A3 mov esi,esp
001E25A5 push 1E1082h
cout << &n << endl;
001E25AA mov edi,esp
001E25AC lea eax,[n]
001E25AF push eax
001E25B0 mov ecx,dword ptr ds:[1EC098h]
001E25B6 call dword ptr ds:[1EC0E8h]
001E25BC cmp edi,esp
001E25BE call __RTC_CheckEsp (01E1159h)
001E25C3 mov ecx,eax
001E25C5 call dword ptr ds:[1EC0ACh]
001E25CB cmp esi,esp
001E25CD call __RTC_CheckEsp (01E1159h)
cout<< p << endl;
001E25D2 mov esi,esp
001E25D4 push 1E1082h
001E25D9 mov edi,esp
001E25DB mov eax,dword ptr [p]
001E25DE push eax
001E25DF mov ecx,dword ptr ds:[1EC098h]
001E25E5 call dword ptr ds:[1EC0E8h]
001E25EB cmp edi,esp
001E25ED call __RTC_CheckEsp (01E1159h)
001E25F2 mov ecx,eax
001E25F4 call dword ptr ds:[1EC0ACh]
001E25FA cmp esi,esp
001E25FC call __RTC_CheckEsp (01E1159h)
cout << *(&n) <<" "<< *(&n + 1 - 1) << endl;
001E2601 mov esi,esp
001E2603 push 1E1082h
001E2608 mov edi,esp
001E260A mov eax,dword ptr [n]
cout << *(&n) <<" "<< *(&n + 1 - 1) << endl;
001E260D push eax
001E260E push 1E8B30h
001E2613 mov ebx,esp
001E2615 push 9 //常量折叠
001E2617 mov ecx,dword ptr ds:[1EC098h]
001E261D call dword ptr ds:[1EC0ECh]
001E2623 cmp ebx,esp
001E2625 call __RTC_CheckEsp (01E1159h)
001E262A push eax
001E262B call std::operator<<<std::char_traits<char> > (01E141Fh)
001E2630 add esp,8
001E2633 mov ecx,eax
001E2635 call dword ptr ds:[1EC0ECh]
001E263B cmp edi,esp
001E263D call __RTC_CheckEsp (01E1159h)
001E2642 mov ecx,eax
001E2644 call dword ptr ds:[1EC0ACh]
001E264A cmp esi,esp
001E264C call __RTC_CheckEsp (01E1159h)
int tp = 5;
001E2651 mov dword ptr [tp],5
cout << *(&tp) << " " << *(&tp + 1 - 1) << endl;
001E2658 mov esi,esp
001E265A push 1E1082h
001E265F mov edi,esp
001E2661 mov eax,dword ptr [tp]
001E2664 push eax
001E2665 push 1E8B30h
001E266A mov ebx,esp
001E266C mov ecx,dword ptr [tp]
001E266F push ecx
001E2670 mov ecx,dword ptr ds:[1EC098h]
001E2676 call dword ptr ds:[1EC0ECh]
001E267C cmp ebx,esp
001E267E call __RTC_CheckEsp (01E1159h)
001E2683 push eax
001E2684 call std::operator<<<std::char_traits<char> > (01E141Fh)
001E2689 add esp,8
001E268C mov ecx,eax
001E268E call dword ptr ds:[1EC0ECh]
001E2694 cmp edi,esp
001E2696 call __RTC_CheckEsp (01E1159h)
001E269B mov ecx,eax
001E269D call dword ptr ds:[1EC0ACh]
001E26A3 cmp esi,esp
001E26A5 call __RTC_CheckEsp (01E1159h)
return 0;
001E26AA xor eax,eax
}
从反汇编可以看出
1.vs2015编译器为const创建的储存空间
2.int *p = (int*)&n; *p = 1; 指针p改变了常量储存空间储存的值
3.在打印的时候,cout<<n<<endl; 编译器将n进行了常量折叠
4.打印*(&n)值时,进行了常量折叠,打印出的值是常量n = 9的值;*(&n + 1 - 1)为进行常量折叠,打印的事n储存空间的值
需要注意的是:常量折叠说的是,在编译阶段,对该变量进行值替换,同时,该常量拥有自己的内存空间,并非像宏定义一样不分配空间
------------------------------------------------------------------------------------------------------------------------------------
以下折抄自《C++编译思想》第八章 常量
C++中的const默认为内部连接,所以不能在一个文件中定义一个const,而在另一个文件中又把它作为extern来引用。为了使const成为外部连接以便让另外一个文件可以对它引用,必须明确地把它定义成exteren,如下面这样:
extern const int x = 1;
注意,通过对它进行初始化并指定为extern,我们强迫给它分配内存(虽然编译器在这里仍然可以选择常量折叠)。初始化使它成为了一个定义而不是声明。在C++中的声明:
extern const int x;
意味着在别处进行了定义。现在明白了为什么C++要求一个const定义时需要初始化:初始化把定义和声明区别开来。当进行了extern const声明时,编译器就不能够进行常量折叠了 ,因为它不知道具体的值。
个人理解:当定义的const为全局变量时,为了使它成为外部连接,需要把它定义成extern,系统会为它分配内存,在定义的文件中可能会进行常量折叠,别的文件不会进行常量折叠。
例2:
//consttest.cpp
void f1();
extern const int gnum = 8;
int main()
{
int pp = gnum;
const int *p = &gnum;
f1();
cout << "end main " << &gnum << endl;
}
//constT1.cpp
extern const int gnum;
void f1()
{
const int *p = &gnum;
int n = gnum;
cout << "f1" << &gnum << endl;
}
看一下反汇编
//consttest.cpp
//constT1.cpp
当前实例中,gnum储存在常量储存区,在gnum定义的文件(consttest.cpp)中gnum进行了常量折叠,在另外文件(constT1.cpp)中使用的是地址里面的内容,地址赋值要求常量储存区需要是指向const的指针,或是引用则需要是const引用(个人见解)
个人笔记,学术不精,有一些尚未理解透彻,学无止境!