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式的強制編譯期計算。


CC BY-SA 4.0

本文使用 CC BY-SA 4.0 授權

標籤:

分類:

更新時間: