符號修飾與函式簽名 extern 「C」

2022-05-05 23:33:15 字數 2749 閱讀 1935

《程式設計師的自我修養》3.5.3以及3.5.4小節。

符號修飾的由來

20世紀70年代以前,編譯器編譯**時產生的目標檔案中,符號名與相應的變數和函式的名字是一樣的,隨著程式語言的發展,例如c語言,如果乙個c語言程式要使用這些庫的話,其自身就不能使用這些庫中已經宣告了的函式和變數的名字作為符號名,否則將會跟現有的目標檔案發生名稱衝突。

為了防止這類符號名衝突,各平台下的程式語言規定了各自的符號生成語法。如c在unix下在函式名和變數前加下劃線作為符號名。這種給函式名增加特定符號來使其符號名唯一的方式就是符號修飾。

這種簡單的符號修飾沒有從根本上解決符號衝突的問題,比如同一種程式語言編寫的目標檔案之間還有可能產生符號衝突,當程式很大時,不同的部門之間也有可能會產生符號衝突,於是c++這類語言開始加上了命名空間(namespace)來解決多模組符號衝突問題。

為了支援c++擁有類、繼承、虛機制、過載、命名空間等這些特性,人們發明了符號修飾修飾(或者稱為符號改編),被修飾之前的函式名稱以及其返回值和引數等資訊,被稱為函式簽名。不同的編譯器採用不同的名字修飾方法,因為導致由不同的編譯器編譯產生目標檔案無法正常相互鏈結。

extern 「c」

c++為了與c相容,在符號的管理上,c++有乙個用來宣告或定乙個c的符號的「extern 」c"」關鍵字用法:

extern"c

"int add(int a, int

b);extern"c

"int

a;//

或者//

extern "c"

//;

注意:extern 「c」中的c必須大寫。

c++編譯器會將在extern 「c」的大括號內(或者後面的)**當作c語言**來處理。所以很明顯,上面的**就是為了將add函式和變數a宣告成c的方式,因為有可能他們的定義是放在.c檔案中的。

舉例:

//

c_code.h file in c_project project

#pragma once

int add(int a, int b);

//

c_code.c file in c_project project

#include "

c_code.h

"int add(int a, int

b)

如果我們有乙個main.c,**如下

#include "

c_code.h

"#include

"stdio.h

"#include

int main(void

)

編譯鏈結執行都沒錯。那麼現在我們把main.c檔案重新命名成main.cpp,**不變,會發生鏈結錯誤: error lnk2019: unresolved external symbol "int __cdecl add(int,int)" (?add@@yahhh@z) referenced in function _main。這裡的鏈結錯誤就是指沒有找到符號「?add@@yahhh@z」。這個符號就是"int __cdecl add(int,int)"修飾後的符號名。

原因是c的符號修飾方式和c++的符號修飾方式不同,導致同樣的宣告會產生不同的符號名。這裡main.cpp是乙個cpp檔案,會使用cpp的方式進行編譯鏈結,而add的定義卻是在.c檔案裡面,因而會鏈結不上。

extern"c

"int add(int a, int b); //

在這裡宣告add函式,不使用c_code.h標頭檔案

int main(void

)

extern"c

"//顯示的將c_code.h所有宣告都使用c方式修飾

#include

"stdio.h

"#include

int main(void

)

以上兩種方式其實是一樣的,這裡因為c_code.h中只有乙個函式,所以使用第一種方法也很簡單,但是如果標頭檔案中有很多函式宣告,使用第二種方法就簡單多了。

由於c++是對c語言的擴充套件,我們常常會需要使用c的庫函式,這些庫函式的定義都是用.c檔案實現的,那麼為了避免每次我們在使用庫函式的時候,都去用extern 「c」關鍵字修飾其標頭檔案,這些標準庫標頭檔案中往往都包含了如下**來解決這個問題。

//

c_code.h file in c_project project

#pragma once#ifdef __cplusplus

extern"c

"#endif

意思是,如果編譯的時候發現__cplusplus巨集已經定義,則給後面的函式宣告都加上extern 「c」,以用c方式修飾,否則不處理。而__cplusplus巨集是c++編譯器在編譯c++程式時預設定義的巨集(其實我們也可以自己在.cpp檔案的頂部定義乙個巨集),顯然,在.cpp檔案中包含這種標頭檔案的時候,就會將這些個函式宣告成用c方式修飾,而如果在.c檔案中包含時,就不會加上extern 「c」修飾。這就解釋了為什麼之前main.c能直接編譯通過,而main.cpp不能的原因。

後記其實這篇博文主要就為了介紹extern 「c」的用法,在網上能搜到很多用法介紹,我也看了好幾篇,但是就是感覺有種不是很透徹的感覺,於是就去找了這本參考書,看完之後就比較明了了。所以說,有些知識看起來很簡單,但是如果不是很透徹理解的話,還是太容易忘記。

符號修飾與函式簽名

p 87 linux下的gcc編譯器中,預設情況下已經去掉了在c語言符號前加 的方式,但是windows平台下的編譯器還保持著在符號前加 的習慣。函式簽名使得函式在目標檔案中的符號變成與其原始檔的函式名 函式引數 所在的類和命名空間及其它資訊關聯了起來。函式簽名經名稱修飾變成修飾後名稱目標檔案中的符...

C 符號修飾和函式簽名

約在20世紀70年代以前,編譯器編譯源 產生目標檔案時,符號名與相應的變數和函式的名字是一樣的。比如乙個彙編源 裡面包含了乙個函式foo,那麼彙編器將它編譯成目標檔案以後,foo在目標檔案中的相對應的符號名也是foo。當後來unix平台和c語言發明時,已經存在了相當多的使用彙編編寫的庫和目標檔案。這...

Python基礎 函式修飾器 符號

def dec f n 3 return f args,kw n dec def foo n return n 2python解析器遇到 且後面跟著函式時,會把函式foo當做引數傳遞給dec函式並執行,即 dec foo n 本例中執行 dec n 2 預設引數一定要用不可變物件,如果是可變物件,執...