標籤:

C語言如何支持C++重載?

之前面試阿里內推的時候面試官問到一個問題:

1.cpp是怎麼支持重載的?

答:編譯器會根據函數名和形參類型生成函數,即編譯器可以通過函數名和形參類型的匹配實現重載,不可以根據返回類型實現重載

2.你認為cpp編譯器是用什麼語言實現的?

答:c語言(當時答的時候也不知道對不對,後來查了一下 gcc確實是用c寫的)

3.既然c語言不可以支持重載,那麼c語言寫的編譯器是如何實現cpp的重載的呢?

答:不清楚

在知乎上搜了一下,基本上只有cpp如何實現重載也就是第一問的答案,沒有第三問的類似問題

斗膽@藍色大大

知乎第一問…如果有哪裡提的不對的請輕拍

更正我的第一問回答,以免對後面有疑惑的人造成誤解

重載使得可以根據作用域,參數類型定義相同函數名的一組重載函數集合,編譯器會根據 作用域和參數類型 在候選函數中選擇可行函數(而不是生成)候選函數,並尋找最佳匹配

把所有的回答都看了,個人總結下,編譯器能實現對功能的支持,與實現編譯器的語言能否對該功能支持是沒有一定關係的。最高票的@莊嚴的回答只是從一個比較機靈的角度回答了這個問題。

引用@莊嚴的回復進一步說明。希望和我有同樣困惑的人也可以看到這一段。

「早先C++之父寫的Cfront,是將C++代碼,轉換為C代碼,然後再調用C語言編譯器真正編譯為機器碼。而無論C++還是C代碼,都是文本,從這個過程看。把一段文字如void foo(int , char) 根據某種規則轉換為 void foo2ic(int, char) 。這過程和(寫這個轉換程序的)C語言自身支持重載不,是沒有關係的。」

應該是面試官由於我前面回答的不準確而提出的一種反問,不能說是出於惡意,重申一遍

我那天遇到的面試官整個過程中都是非常友好的

面試最後被面者提問題的環節也給了我指出了他一些看到的面試過程的不足,以及改進的建議。面試掛了,但非常感謝他花了那麼多時間和我交流,希望不要因為這樣面試者個人知識上不足的問題而造成對面試官的誤解。

問題提出到現在,收到了許多程序猿前輩的回答,自己反思了一下應該去進一步複習紮實基礎,謝謝大家。


  1. 所有圖靈完備的編程語言都能編寫C++編譯器

  2. C語言是圖靈完備的編程語言

  3. C語言能編寫C++編譯器


簡單來講,你可以理解為把

void Fuck(int);
int Fuck(float, const string);

的名字改成

Fuck%shit0%int%bitch%void
Fuck%shit0%float%shit1%&&>%bitch%int

然後把代碼裡面對Fuck的引用按照編譯出來的結果改成上面這兩個,就相當於是C語言實現重載了。


問:C++ 有約炮功能嗎?

答:沒聽說過。

問:QQ是怎麼實現約炮的?

答:這個我知道,你搜索附近的人,然後。。。。。。

問: QQ用什麼語言寫的?

答:C++

那麼問題來了: 沒有約炮功能的C++,是怎麼實現有約炮功能的QQ的。


神一般的阿里面試官,整個一外行的幹活。

愛咋實現咋實現,歸根到底就是個詞法分析和語法分析的編譯器前端實現問題,你高興的話用二進位實現都沒人管得了


1. 主要利用Name Mangling來支持重載

2. 實現編譯器和使用的語言無關,目前來看,基本上是使用C++來實現C++編譯器

3. Name Mangling是有一套規則的,

參見:

【1】Itanium C++ ABI

【2】clang/ItaniumMangle.cpp at master · llvm-mirror/clang · GitHub

4. 補充一個isocpp的鏈接,提到了Name Mangling支持Function Overloading:

Standard C++


其實,問題2你就已經錯了。gcc現在是用C++寫的,vc和clang也是。

問題3答不出來,只能說你對重載的本質理解不夠透徹,是該掛。


面試官如果不糊塗的話,他就是想考驗一下你糊塗不糊塗。

"3.那麼問題來了,既然c語言不可以支持重載,那麼c語言寫的編譯器是如何實現cpp的重載的呢?"

答:什麼問題來了,這和前面的問題毫無關係。基本上用什麼語言都可以寫一個cpp的編譯器,實現cpp的重載。用basic、pascal、lua、php、js什麼的都可以做cpp的編譯器。如果不嫌麻煩,彙編也可以。本質上,編譯器只是將輸入的一串字元通過運算輸出一串二進位數而已,能實現基本演算法的語言都足以勝任。所謂重載,只是輸出的那串二進位數中的具體格式罷了。


已經更新了很多不錯的答案了, 不過面試官提的一個問題還是挺好的 值得深入挖掘一下, 且我的面試經驗很少很少, 就當回答面試官回答一下順便整理下的思路

1. cpp是怎麼支持重載的?

就像好多同學提到的`name mangling`機制 或者叫做`name decoration`, 實際上不過是c語言中用函數名稱作為標籤, c++中把函數更多的屬性作為標籤而已, 經過編譯器處理後會生成修時候的名稱(符號名); c語言中因為只用到了函數名作為標籤, 因此符號名就可以用函數名代替,後來考慮到會和庫的符號名衝突,不同的編譯器又對這個符號進行了加工,比如前面加個下劃線_*

繼續說回c++, 舉例,我寫了一個函數

istream read_hw(istream is, vector& hw)

對於c語言來說, 函數的符號不過是對函數名read_hw的處理, c++會把參數也算進來作為標籤,那編譯器處理之後可以通過如下方式查看符號是啥樣的

g++ -c *.cpp //生成目標文件.o
nm *.o //查看目標文件中的符號表

可以看到, 上面的函數在.o文件中生成符號名為:`_Z7read_hwRSiRSt6vectorIdSaIdEE`

這啥玩意兒啊? 沒關係,只要根據函數名搜索下就能找到, 至於其他亂七八糟的字母不過是根據編譯器給定的規則生成對不同類型的標示(大可不必管,不同平台生成規則是不一樣的,visual c++的規則是對外不公開的,不過提供了反查的介面), 如果需要看這個符號名原始函數是什麼,可以通過如下命令實現:

c++filt _Z7read_hwRSiRSt6vectorIdSaIdEE
read_hw(std::basic_istream& &>, std::vector& &>)

這道題應該回答的就比較完整了 面試官另外個問題我感覺應該是隨口問的。我再自問自答兩個自己也不是特別清楚的問題,有明白的大神可以指點一二。

2. 為什麼c沒有支持重載?

有同學說是因為,c如果支持重載了,那麼會造成`ABI`不兼容,可實際上,c++的重載特性本身就是`ABI`不兼容的原因之一, 即不同的編譯器生成的符號修飾符標準規則都是差異的,造成鏈接錯誤那也是必然的; 激進如c++(我們連c++11都沒有用過現在都17了),不同的派系visual c++和gcc目前是互不兼容,不過終究應該會向著統一標準的方向去發展, c語言在等著這一天?然後順手把函數重載特性實現

3. 除了c, python/java等能實現c++編譯器么?

@張元亮 的意見是不能

java/python這類語言是不能完全實現C++編譯器的,只能實現詞法分析,語法分析,語義分析,等與機器無關的部分,但是和機器密切相關的目標代
碼即.o文件是不能用python/java實現的,java/python本身已經被解釋器和物理機隔離開來。所以C++編譯器不能用他們完全實現

我的理解, 機器無關部分自不必說, 機器相關的部分無外乎不同平台寄存器、數據類型字長的區別, 就算是gcc的編譯器內部實現也要支持不同的硬體平台生成不同的彙編文件,沒搞明白這裡python等語言怎麼就不行了。

4. 既然c++ c語言的編譯器有不同符號生成規則,如何相互鏈接?

-c++提供c語言介面的話, `extern` 「C」, 標示按照c語言的符號生成規則提供介面

-如果不知道一個介面是提供給c語言用 還是c++用, 利用`__cplusplus`宏定義來區分.

實踐上, 還是需要用到的源文件, 都用一個平台以及版本編譯比較靠譜, 才發現c++二進位兼容這塊的坑好大.


第三問要是問我我還真不知道該怎麼回答。

不支持變數名的彙編是如何寫出支持變數名的pascal編譯器的


在c++中類的成員函數會被內化為一個nonmember函數,也就是說成員函數與非成員函數在調用效率上上並沒有區別。而將原本的成員函數重寫成非成員函數,會有一個name mangling過程,就是對函數的名字進行特殊處理,使之成為程序中獨一無二的,用以區別不同類中同名的函數,以及同一類中的重載函數。

例如:

Class person

{

private:

string name;

public:

void name(string name);

string name();

}

這個類中的兩個函數也許會被修改成這樣(不同編譯器的修改命名方法不同,但目的都是得到全局唯一的名字):

void name_3personFs(string name);

string name_3personFv();

這就轉換成為兩個獨一無二的函數了,所以無論使用C、C++或者其它語言實現的編譯器,實現上述類似的功能應該都沒有什麼問題吧。


水平不夠,但不請自來。對編譯器了解不夠,請允許我從另外一個角度答題。

如果我是面試官,我可能並不在乎編譯器是怎麼實現重載這一點。而在於你如何實現這樣的要求。

用c寫函數重載可以么?答案是可以,我之前曾經用c語言用結構體山寨過OO編程,用一個char(標識參數類型)和一個union(參數內容)的結構體來傳遞參數,把各種實現寫成static函數,再把wrapper寫到頭文件里就好。在外部調用時看起來就像函數重載了。你看,這並不難,根本不算奇技淫巧。

C語言的確不支持函數重載,不過需要的話你可以自己山寨一個。

個人覺得莊嚴就是想吐這個槽吧。

答主還在寫C語言的時候,還山寨過new和delete,繼承也山寨過一部分,還山寨過qt的signal/slot機制。。。年輕就是這麼任(nao)性(can)。


第三問是考智商的,但是比較片面。

為什麼說片面呢?因為答對了不能說明智商高,但答不到點上卻說明智商低。

面試官應該是故意為之的。如果不是故意的,而是認真的問出這個問題的話,那麼將很難解釋清楚。

就像說了一大通,然後問題來了,挖掘機技術哪家強?


我覺得面試官就是單純的想知道你是不是在背題吧?


第三題的答案就是第一題,這明顯是在考察面試者對知識的掌握深度。面試官玩了個花活就把題主繞暈了


有一句話說的特別好:可以用任何語言實現java (不要在意細節)


學過一點編譯原理。一種語言的特性和實現它的編譯器用的語言有什麼關係嗎?


重載不就是相當於吧參數類型寫到了函數名上么


男性如何支持女性的生殖能力?


編譯器而已,最本質工作就是將源碼翻譯成機器碼,本質還是從一類文件生成另一種文件,就像bmp轉jpg一樣,和語言無關,理論上你用java也能寫C++編譯器,C++也能自己寫自己,你的問題本身就有問題,不會開車的人能不能造汽車??? windows編譯的程序能不能再linux上運行,這都不是問題。。。這些問題表面上看似有聯繫,好像有鴻溝,其實根本就沒有任何互斥的關係。。。

因為就是兩個耗不想乾的因果。。。理論上你就是用C51運行的編譯器,編譯windows程序也沒問題,編譯和運行時兩碼事,C寫C++編譯器,和讓C編譯器去分析C++源碼,本身也是無關的兩碼事。。。


強行吐槽一下。

即編譯器可以通過函數名和形參類型的匹配實現重載,不可以根據返回類型實現重載

誰說編譯器 不可以 根據返回值的類型重載的?只是 C++ 設計上沒有把返回值當作重載條件的一部分而已,語言語義上僅僅根據返回值不同來實現重載是沒有任何問題的。

比如 Swift 有接近的用法(當然這裡是表達式不是函數調用):

var s: String = "s" // is of type String.
var c: Character = "s" // is of type Character.


推薦閱讀:

如何寫一個簡單的編譯器?
如何從Apple提供的源代碼編譯objc runtime?
Xcode工程設置裡面編譯器選項為啥沒有GCC?
clang分析源碼前判斷是按C模式還是按C++模式分析的過程?

TAG:編譯器 |