C const 與 C++ const 的不同及常見錯誤
const 可見性的區別
對於符號的鏈接可見性:在 C 中,const 全局變量默認為 external;而在 C++中則默認為 internal。
C
main.c:
#include <stdio.h>
#include <stdlib.h>
int
main()
{
extern const int x;
printf("%d\n", x);
return EXIT_SUCCESS;
}
x.c:
const int x = 1000;
gcc main.c x.c -std=gnu99 -Wall -Wextra
可編譯成功,運行後輸出1000
。
原因:在 C 中,函數與全局變量的聲明默認均為外部可見,即編譯器默認將 x.c 中的const int x = 1000;
視作extern const int x = 1000;
。
C++
C++處理 const 全局變量的可見性規則與 C 相反。
main.cc:
#include <stdio.h>
#include <stdlib.h>
int
main()
{
extern const int x;
printf("%d\n", x);
return EXIT_SUCCESS;
}
x.cc:
const int x = 1000;
g++ main.cc x.cc -std=c++11 -pedantic -Wall -Wextra
編譯則會報錯:
Undefined symbols for architecture x86_64:
"_x", referenced from:
_main in ccF60bAb.o
ld: symbol(s) not found for architecture x86_64
collect2: error: ld returned 1 exit status
這是因為在 C++中,const 變量均默認為內部可見,要使得 x.cc 中的const int x
外部可見,須在其前面聲明extern
。
改寫 x.cc
extern const int x = 1000;
再次使用 g++編譯則成功,運行後可輸出1000
。
extern 引用運行時問題
在 C/C++中,使用 extern 引用一個外部聲明的變量時,可以用非 const 的辦法引用 const 變量。
這不會導致編譯時錯誤,但會導致運行時錯誤。
main.c
#include <stdio.h>
#include <stdlib.h>
int
main()
{
extern int x;
printf("%d\n", x);
++x;
printf("%d\n", x);
return EXIT_SUCCESS;
}
x.c
const int x = 1000;
gcc main.c x.c -std=gnu99 -Wall -Wextra
可編譯成功。但運行時出錯:
❯ ./a.out
1000
zsh: bus error ./a.out
可惜的是,這種跨文件的引用 bug,編譯器很難檢測出來,只有在運行後才會發生奇怪的後果。所以對待全局 const 變量,一定要非常小心。
C89/{C99,C++} const 區別
在 C99 和 C++中,const 關鍵字聲明的變量可以用來初始化數組長度(此節內容均不討論 vla 擴展,「數組」一詞亦不指 vla 數組):
#include <stdlib.h>
int
main()
{
const int kArraySiz = 100;
int arry[kArraySiz];
return EXIT_SUCCESS;
}
這段代碼既是合法的 ISO C99 代碼,也是合法的 ISO C++代碼,可以嚴格編譯通過並運行成功。
但在 ISO C89 中,這段代碼錯誤,因為 C89 認為:const 關鍵字的作用為 標記 變量 kArraySiz 不可修改,從機理上說,是被標記為不可變的變量 kArraySiz 在運行期被初始化為 100,所以在編譯期無從得知其數值,故無法將其用於在編譯期指定數組長度。
這個限制儘管在語義上有一定道理,但在工程上帶來了麻煩,所以 C++移除了此限制,允許 const 變量用來初始化數組長度,後來的 C99 標準也吸納了 C++這一做法。
由於這種做法把 const 變量的作用給提前到編譯期,一定程度上違背了 const 的原始語義,所以 C++11 及更新標準提倡使用 constexpr 關鍵字代替 const 來表示必須在編譯期計算的表達式。但我們依然要知道這條關於 const 的例外規則,即在 C++/C99 中 const 也可以用來在編譯期初始化數組。目前大量的 C/C++代碼依然依賴這種特殊的 const 行為而非更嚴格的 constexpr,不過我們應該在新代碼裡有意多使用 constexpr。
const != constant
在 C/C++中,const 並不如其語義上可靠,比如這種用內存地址來修改 const 量的代碼可以順利編譯:
const int a = 10;
int *p_a = &a;
*p_a = 100;
printf("%d\n", a);
運行後,這段代碼可能會輸出100
,也可能輸出其它數字,因為這種代碼在 C 中是未定義行為,C 語言不保證此行為的後果。
謹記:C/C++的 const 是對變量的不可變標記,不是用來聲明常量的工具。真正的常量 是指在編譯期可計算出準確數值的量,即要麼是#define
式的預處理文本替換,要麼是unscoped enumerator
,要麼是constexpr
式的強制編譯期計算。