[英]C++: Compiler and Linker functionality
我想確切地了解程序編譯器的哪個部分以及鏈接器所關注的內容。 所以我寫了下面的代碼:
#include <iostream>
using namespace std;
#include <string>
class Test {
private:
int i;
public:
Test(int val) {i=val ;}
void DefinedCorrectFunction(int val);
void DefinedIncorrectFunction(int val);
void NonDefinedFunction(int val);
template <class paramType>
void FunctionTemplate (paramType val) { i = val }
};
void Test::DefinedCorrectFunction(int val)
{
i = val;
}
void Test::DefinedIncorrectFunction(int val)
{
i = val
}
void main()
{
Test testObject(1);
//testObject.NonDefinedFunction(2);
//testObject.FunctionTemplate<int>(2);
}
我有三個功能:
FunctionTemplate - 功能模板。
現在如果我編譯這段代碼,我會在DefinedIncorrectFunction中找到缺少';'的編譯器錯誤。
假設我修復此問題然后注釋掉testObject.NonDefinedFunction(2)。 現在我收到鏈接器錯誤。 現在注釋掉testObject.FunctionTemplate(2)。 現在我收到了缺少';'的編譯器錯誤。
對於函數模板,我理解編譯器不會觸及它們,除非在代碼中調用它們。 所以缺少';' 在我調用testObject.FunctionTemplate(2)之前,編譯器沒有抱怨。
對於testObject.NonDefinedFunction(2),編譯器沒有抱怨,但鏈接器沒有。 據我所知,所有編譯器都關心是知道這是一個聲明的NonDefinedFunction函數。 它並不關心實施。 然后鏈接器抱怨,因為它無法找到實現。 到現在為止還挺好。
我感到困惑的地方是編譯器抱怨DefinedIncorrectFunction。 它沒有尋找NonDefinedFunction的實現,但是它經歷了DefinedIncorrectFunction。
所以我不清楚編譯器究竟做了什么以及鏈接器做了什么。 我的理解是鏈接器鏈接組件與他們的調用。 因此,當調用NonDefinedFunction時,它會查找NonDefinedFunction的編譯實現並抱怨。 但是編譯器並不關心NonDefinedFunction的實現,但是它確實用於DefinedIncorrectFunction。
如果有人可以解釋或提供一些參考,我真的很感激。
謝謝。
編譯器的功能是編譯您編寫的代碼並將其轉換為目標文件。 所以,如果你錯過了一個;
或使用未定義的變量,編譯器會抱怨,因為這些是語法錯誤。
如果編譯順利進行,則會生成目標文件 。 目標文件具有復雜的結構,但基本上包含五件事
編譯器編譯代碼並使用它遇到的每個符號填充符號表。 符號指的是變量和函數。 這個問題的答案解釋了符號表。
它包含鏈接器可以處理到工作應用程序或共享庫中的可執行代碼和數據的集合。 目標文件中有一個稱為符號表的數據結構,它將目標文件中的不同項映射到鏈接器可以理解的名稱。
需要注意的一點
如果從代碼中調用函數,則編譯器不會將例程的最終地址放在目標文件中。 相反,它將占位符值放入代碼中,並添加一個注釋,告訴鏈接器在其處理的所有目標文件中查找各種符號表中的引用,並將最終位置保留在那里。
生成的目標文件由鏈接器處理,鏈接器將填充符號表中的空白,將一個模塊鏈接到另一個模塊,最后提供可由加載程序加載的可執行代碼。
所以在你的具體情況下 -
undefined reference to NonDefinedFunction
錯誤的undefined reference to NonDefinedFunction
中止,因為它無法找到對相關符號表條目的引用。 為了進一步理解它,可以說你的代碼結構如下
File- try.h
#include<string>
#include<iostream>
class Test {
private:
int i;
public:
Test(int val) {i=val ;}
void DefinedCorrectFunction(int val);
void DefinedIncorrectFunction(int val);
void NonDefinedFunction(int val);
template <class paramType>
void FunctionTemplate (paramType val) { i = val; }
};
文件try.cpp
#include "try.h"
void Test::DefinedCorrectFunction(int val)
{
i = val;
}
void Test::DefinedIncorrectFunction(int val)
{
i = val;
}
int main()
{
Test testObject(1);
testObject.NonDefinedFunction(2);
//testObject.FunctionTemplate<int>(2);
return 0;
}
讓我們首先只復制並組裝代碼但不鏈接它
$g++ -c try.cpp -o try.o
$
該步驟沒有任何問題。 所以你在try.o中有目標代碼。 讓我們嘗試將其鏈接起來
$g++ try.o
try.o: In function `main':
try.cpp:(.text+0x52): undefined reference to `Test::NonDefinedFunction(int)'
collect2: ld returned 1 exit status
你忘了定義Test :: NonDefinedFunction。 我們在一個單獨的文件中定義它。
File- try1.cpp
#include "try.h"
void Test::NonDefinedFunction(int val)
{
i = val;
}
讓我們將其編譯成目標代碼
$ g++ -c try1.cpp -o try1.o
$
它再次成功。 讓我們嘗試僅鏈接此文件
$ g++ try1.o
/usr/lib/gcc/x86_64-redhat-linux/4.4.5/../../../../lib64/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: ld returned 1 exit status
沒有主要因此贏得了't鏈接!!
現在,您有兩個單獨的對象代碼,其中包含您需要的所有組件。 只需將它們兩個傳遞給鏈接器,然后讓它完成剩下的工作
$ g++ try.o try1.o
$
沒錯! 這是因為鏈接器找到所有函數的定義(即使它分散在不同的目標文件中)並使用適當的值填充目標代碼中的空白
我相信這是你的問題:
我感到困惑的地方是編譯器抱怨DefinedIncorrectFunction。 它沒有尋找NonDefinedFunction的實現,但是它經歷了DefinedIncorrectFunction。
編譯器試圖解析DefinedIncorrectFunction
(因為你在這個源文件中提供了一個定義)並且存在語法錯誤(缺少分號)。 另一方面,編譯器從未看到NonDefinedFunction
的定義,因為此模塊中根本沒有代碼。 您可能在另一個源文件中提供了NonDefinedFunction
的定義,但編譯器不知道。 編譯器一次只查看一個源文件(及其包含的頭文件)。
說你想吃點湯,所以你去餐館。
你在菜單上搜索湯。 如果您沒有在菜單中找到它,請離開餐廳。 (有點像編譯器抱怨它無法找到功能)如果你找到它,你會怎么做?
你打電話給服務員去給你點湯。 然而,僅僅因為它在菜單中,並不意味着它們也在廚房里。 可能是一個過時的菜單,可能是有人忘了告訴廚師他應該做湯。 再說一次,你離開了。 (就像來自鏈接器的錯誤,它找不到符號)
編譯器檢查源代碼是否符合語言並遵守語言的語義。 編譯器的輸出是目標代碼。
鏈接器將不同的對象模塊鏈接在一起以形成exe。 函數的定義位於此階段,並在此階段添加調用它們的相應代碼。
編譯器以翻譯單元的形式編譯代碼。 它將編譯源.cpp
文件中包含的所有代碼,
DefinedIncorrectFunction()
在源文件中定義,因此編譯器會檢查它的語言有效性。
NonDefinedFunction()
在源文件中有任何定義,因此編譯器不需要編譯它,如果定義存在於某個其他源文件中,該函數將被編譯為該轉換單元的一部分,並且鏈接器將進一步鏈接對於它,如果在鏈接階段鏈接器找不到定義,那么它將引發鏈接錯誤。
編譯器的作用以及鏈接器的作用取決於實現:合法實現可以將標記化的源存儲在“編譯器”中,並在鏈接器中執行所有操作。 現代實現確實會越來越多地推斷鏈接器,以實現更好的優化。 許多模板的早期實現甚至在鏈接時間之前都沒有查看模板代碼,除了匹配大括號足以知道模板結束的位置。 從用戶的角度來看,您對錯誤“是否需要診斷”(可由編譯器或鏈接器發出)或未定義的行為更感興趣。
在DefinedIncorrectFunction
的情況下,您提供了需要解析實現的源文本。 該文本包含需要診斷的錯誤。 對於NonDefinedFunction
:如果使用該函數,則無法在完整程序中提供定義(或提供多個定義)違反了一個定義規則,即未定義的行為。 不需要診斷(但是我無法想象一個實現沒有為缺少的函數定義提供一個實現)。
在實踐中,簡單地通過檢查單個翻譯單元的文本輸入可以容易地檢測到的錯誤由標准定義為“需要診斷”,並且將由編譯器檢測。 通過檢查單個翻譯單元無法檢測到的錯誤(例如,缺失的定義,可能存在於不同的翻譯單元中)是正式未定義的行為 - 在許多情況下,錯誤可以由鏈接器檢測到,並且在這種情況下例如,實現實際上會發出錯誤。
這在內聯函數的情況下有所修改,在內聯函數中,您可以在每個轉換單元中重復定義,並且由模板進行極大修改,因為在實例化之前無法檢測到許多錯誤。 對於模板,標准使實現具有很大的自由度:至少,編譯器必須足夠解析模板以確定模板的結束位置。 然而,標准添加了諸如typename
東西,以便在實例化之前允許更多的解析。 然而,在依賴上下文中,在實例化之前不可能檢測到一些錯誤,這可能發生在編譯時或鏈接時 - 早期實現有利於鏈接時實例化; 編譯時實例化在今天占主導地位,並由VC ++和g ++使用。
缺少的分號是語法錯誤,因此代碼不應該編譯。 即使在模板實現中也可能發生這種情況。 從本質上講,有一個解析階段,雖然對人類來說很明顯如何“修復和恢復”編譯器不必這樣做。 它不能只是“想象分叉是否存在,因為這就是你的意思”並繼續。
鏈接器查找函數定義以在需要它們的地方調用。 這里不需要,所以沒有投訴。 這個文件中沒有錯誤,因為即使它是必需的,它也可能不會在這個特定的編譯單元中實現。 鏈接器負責收集不同的編譯單元,即“鏈接”它們。
啊,但你可以在另一個編譯單元中使用NonDefinedFunction(int)。
編譯器為鏈接器生成一些輸出,基本上說明以下內容(除其他外):
鏈接器用於鏈接在外部模塊中定義的代碼(可能) - 您將與此特定源文件一起使用的庫或目標文件,以生成完整的可執行文件。 因此,如果您有一個聲明但沒有定義,您的代碼將編譯,因為編譯器知道鏈接器可能在其他地方找到丟失的代碼並使其工作。 因此,在這種情況下,您將從鏈接器而不是編譯器獲得錯誤。
另一方面,如果代碼中存在語法錯誤,則編譯器甚至無法編譯,您將在此階段遇到錯誤。 宏和模板的行為可能有點不同,如果不使用它們就不會導致錯誤(模板與具有更好界面的宏一樣多),但它也取決於錯誤的重力。 如果你陷入困境,編譯器無法弄清楚模板化/宏代碼結束和常規代碼啟動的地方,它將無法編譯。
使用常規代碼,編譯器必須編譯甚至死代碼(源文件中未引用的代碼),因為有人可能希望通過將.o文件鏈接到他的代碼來使用來自其他源文件的代碼。 因此,非模板化/宏代碼必須在語法上正確,即使它不直接在同一源文件中使用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.