連結器規則會引入的巨坑
首先來看一個簡單的程式。下面是是兩段程式,分別放在link.c
和bar.c
中。
/* link.c */
#include<stdio.h>
void f(void);
int x = 13;
int main()
{
f();
printf("x=%d\n", x);
return 0;
}
/* bar.c */
int x;
void f()
{
x = 12;
}
現在我們使用命令gcc -o linkbar link.c bar.c
將其編譯為可執行檔案,然後用./linkbar
執行檔案,執行結果為:x=12
。
有趣的事情就發生了,在link.c
中分明定義的是x = 13
,而且列印語句使用的也是自己模組的x
變數,怎麼就變成了 12 了呢?
那問題是不是就出在bar.c
檔案中呢?這個檔案中也定義了x
變數,並且在f()
函式中將其賦值了,但是這個x
是在bar.c
中,link.c
中也有自己的x
變數,按理來說它們應該是相互不影響的,讓人疑惑!!!!
實際上這都是連結器搞的鬼,上面場景在工作中遇到的可能性不小,這種錯誤引入程式後,並不會立即表現出來,而是可能在其它你想不到的地方報錯,試想一下,在一個擁有成百上千個模組的大型系統中,發生了這樣的錯誤,而你也不知道錯誤的源頭,讓你定位出這個錯誤,其困難程度可想而知。
要理清這個問題,需要去了解連結器是怎麼工作的。我們都知道,現在的系統越來越大,我們將其分解成為更小的、更好管理的模組,可以獨立地修改和編譯這些模組(像不像微服務?),這樣協作讓我們不必將整個應用程式組織成一個巨大的原始檔。
為了構造可執行檔案,連結器需要完成符號解析
和重定位
兩個主要任務,這裡我們主要看看符號解析。
每個符號都對應一個函式、一個全域性變數或一個靜態變數(C 中以static
宣告的變數),符號解析就是要把每個符號引用與符號定義關聯起來,注意不包括區域性變數哦。
C 語言中 static 宣告的變數和 Java、C++ 中的 private 宣告一樣,不帶 static 的就是 public 型別的。
你肯定聽說過可重定位
這個詞,源程式在經過預處理、編譯、彙編之後產生的就是可重定位目標檔案
,怎麼理解可重定位呢?簡單來說,就是說檔案裡面的程式碼段和資料的地址還沒有最終確定。
在每個可重定位目標檔案中都有一個符號表,這個符號表包含了可重定位目標檔案自己定義和引用的符號資訊。共有三種不同的符號:1、由自己定義並能被其它模組引用的全域性符號;2、由其它模組定義並被自己引用的全域性符號;3、只能被自己引用的區域性符號。
連結器解析符號引用是將每個引用與它的輸入的可重定位目標檔案的符號表中的一個確定的符號定義關聯起來,但是不同的可重定位目標檔案可能有多個同名的全域性符號,即多重定義的全域性符號。
函式和已經初始化的全域性變數是強符號
,未初始化的全域性變數是弱符號
。根據強弱符號的定義,Linux 連結器使用三條規則來處理多重定義的符號。
- 不允許有多個同名的強符號;
- 如果有多個弱符號和一個強符號同名,那麼選擇強符號;
- 如果有多個弱符號同名,那麼選擇其中任意一個。
看到這裡,就明白為什麼會有開篇程式出現的那個錯誤了,因為它正好滿足規則二,所以bar.c
中的x
變數實際上還是link.c
中的x
變數。
尤其規則 2 和 3 的應用會帶來一些不易察覺的執行時錯誤,這是非常難理解的,尤其是重複的符號中還有不同的型別時,比如下面這個例子。
/* link.c */
#include<stdio.h>
void f(void);
int y = 12;
int x = 13;
int main()
{
f();
printf("x=0x%x y=0x%x \n", x, y);
return 0;
}
/* bar.c */
double x;
void f()
{
x = -0.0;
}
在一臺 x86-64/Linux 機器上,double
型別是 8 個位元組,而int
型別是 4 個位元組,假設系統中x
的地址是0x601020
,y
的地址是0x601024
,而bar.c
的賦值x = -0.0;
將會用負零的雙精度浮點表示覆蓋記憶體中x
和y
的位置。
相關文章
- Mac 下打包APK的血淚坑(巨坑,巨巨坑,史前巨坑)MacAPK
- 我們都會知道的Javascript:this的繫結規則JavaScript
- C++ include標頭檔案引入規則C++
- javascript ==與!=的比較規則(加踩坑)JavaScript
- JS中this的繫結規則JS
- 矛盾與規則的結算
- JavaScript中this的繫結規則JavaScript
- JavaScript this 繫結規則JavaScript
- springboot引入mybatis遇到的坑Spring BootMyBatis
- Vue3 初體驗(中),巨坑的事件繫結Vue事件
- MySQL觸發器的使用規則MySql觸發器
- JS中this的4種繫結規則JS
- 規則引擎與機器學習比較與結合機器學習
- 常見的社會潛規則有哪些?
- Vue系列-import動態引入的坑VueImport
- springboot對接dubbo遇到的巨坑Spring Boot
- Chaining If Else Statements 巨坑的題目AI
- 【Unity】XLua巨坑彙總Unity
- 最佳化器-RBO 的規則轉化
- ESLint裡的規則教會我,無規矩 不程式設計EsLint程式設計
- 網路分流器-網路分流器-TCP重組和會話規則TCP會話
- c++的連結器C++
- 事件繫結和樣式規定的原則事件
- CSS樣式規則-CSS結構的特點CSS
- 匹配磁力連結的正規表示式
- SparkR連結mysql資料庫(踩坑)SparkMySql資料庫
- Git忽略提交規則.gitignore配置總結Git
- Drools規則引擎實踐直白總結
- 基於 XAF Blazor 的規則引擎編輯器Blazor
- Activity的啟動模式及IntentFilter匹配規則總結模式IntentFilter
- CSS樣式規則之CSS結構的特點CSS
- 連結伺服器的位置伺服器
- 如何開會-羅伯特議事規則
- 規則
- .net core Web API引數繫結規則WebAPI
- 淺談連結器
- java虛擬機器規範-載入、連結與初始化Java虛擬機
- 提取超連結正規表示式