天天看點

為什麼extern使用const修飾的變量會編譯不過?

const變量能被其他檔案extern引用嗎?為什麼?

先來看一段代碼:

// main.cc
#include<stdio.h>
// 引用外部定義的const_int變量
extern const int const_int;
int main()
{
    printf("const_int:%d\n",const_int);
    return 0;
}
// const.cc
// 定義const 變量
const int const_int  = 10;      

編譯連結:

$ g++ -o main main.cc const.cc
 /tmp/ccWHAXxB.o: In function `main':
main.cc:(.text+0x6): undefined reference to `const_int'
collect2: error: ld returned 1 exit status      

我們發現出現了連結問題,說const_int沒有定義的引用,但我們确實在​​http://const.cc​​檔案中定義了。

我們去掉const修飾符再編譯一次,發現是可以的。從上面這個編譯問題,就引出今天要講的内容了。至于為什麼會編譯不過,最後再做解釋。

當然你會發現,按照C代碼去編譯,是可以編譯出來的。後面再解釋。

連結屬性

我們都知道,C/C++代碼的編譯通常經過預編譯,彙編,編譯,連結(參考​​hello程式是怎麼生成的​​)通常會有變量會有三種連結屬性:外部連結,内部連結或無連結。

具有函數作用域,塊作用域或者函數原型作用域的變量都是無連結變量;具有檔案作用域的變量可以是内部連結也可以是外部連結。而外部連結變量可以在多個檔案中使用,内部連結變量隻能在一個編譯單元中使用(一個源代碼檔案和它包含的頭檔案)。

關于作用域,也可以參考《​​全局變量,靜态全局變量,局部變量,靜态局部變量​​​》。

說了這麼多,舉個具體的例子:

#include<stdio.h>
 int external_link = 10;  // 檔案作用域,外部連結
 static internal_link = 20; // 檔案作用域,内部連結
 int main()
 {
     int no_link = 30;   // 無連結
     printf("%d %d %d \n",external_link,internal_link,no_link);
     return 0;
 }      

這裡無連結變量還是比較好區分的,隻要不是檔案作用域的變量,基本是無連結屬性。而檔案作用域變量是内部連結還是外部連結呢?隻要看前面是否有static修飾即可。當然對于C++,還要看是否有const修飾,後面我們再說。

如何知道某個變量是什麼連結屬性?

舉個例子,在前面的代碼中,我們按照C代碼進行編譯:

$ gcc -c const.c 
 $ nm const.o |grep const_int
 0000000000000000 R const_int      

nm指令在《​​linux常用指令-開發調試篇​​》中略有介紹,它可以用來檢視ELF檔案的符号資訊。

從這裡的結果可以看到const_int前面是R修飾的,

R:該符号位于隻讀資料區,READONLY的含義

而該字母大寫,其實也是表示它具有外部連結屬性。

再看看按照C++代碼編譯:

$ g++ -c const.c
 $ nm const.o |grep const_int
 0000000000000000 r _ZL9const_int      

可以看到,它的修飾符也是r,但是是小寫的r,小寫字母表示該變量具有内部連結屬性。

nm指令非常實用,但本文不是重點。

const關鍵字

說到const關鍵字,在《​​const關鍵字到底該怎麼用​​​》和《​​C++中的const與C中的const有何差别?​​》中已經分析過了,這裡簡單說一下,被const關鍵字修飾的變量,表明它是隻讀的,不希望被修改。

extern關鍵字

extern關鍵字可以引用外部的定義,想必很多朋友已經很熟悉了,舉個例子,如果把最開始的例子中的const關鍵字去掉,​​http://main.cc​​中的extern的意思,就是說有一個const_int變量,但是它在别的地方定義的,是以這裡extern修飾一下,這樣在連結階段,它就會去其他的編譯單元中找到它的定義,并連結。

當然,還有一個不太被關注的作用是,在C++中,它可以改變const變量的連結屬性。

是的,在C++中,它改變了const_int的連結屬性。我們可以修改const.c的内容如下:

extern const int const_int  = 10;      

然後再檢視一下:

$ nm const.o |grep const_int
 0000000000000000 R const_int      

發現沒有,它前面的修飾變成大寫的R了,是以這個時候,你再編譯,就能編譯過,而不會報錯了,對于C,它本來就是外部連結屬性,是以根本不會報錯。

extern還有另外一個用法:

《​​​C++是如何調用C接口的​​》?

解疑

  • 未定義
  • 在其他地方定義了,但是不具備外部連結屬性
  • 定義了,具備外部連結屬性,但是連結順序有問題

總結

  • 作用域 --《​​全局變量,靜态全局變量,局部變量,靜态局部變量​​》
  • const關鍵字--《​​const關鍵字到底該怎麼用​​》
  • extern關鍵字
  • nm檢視符号表