亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C++ > 正文

解析C語言與C++的編譯模型

2020-05-23 14:03:20
字體:
來源:轉載
供稿:網友
C++繼承了C的編譯模型,C語言的編譯鏈接模型相對簡潔,但C++繼承了這些機制之后變得更加復雜難以理解,這里就來帶大家簡要解析C語言與C++的編譯模型
 

首先簡要介紹一下C的編譯模型:
限于當時的硬件條件,C編譯器不能夠在內存里一次性地裝載所有程序代碼,而需要將代碼分為多個源文件,并且分別編譯。并且由于內存限制,編譯器本身也不能太大,因此需要分為多個可執行文件,進行分階段的編譯。在早期一共包括7個可執行文件:cc(調用其它可執行文件),cpp(預處理器),c0(生成中間文件),c1(生成匯編文件),c2(優化,可選),as(匯編器,生成目標文件),ld(鏈接器)。
1. 隱式函數聲明
為了在減少內存使用的情況下實現分離編譯,C語言還支持”隱式函數聲明”,即代碼在使用前文未定義的函數時,編譯器不會檢查函數原型,編譯器假定該函數存在并且被正確調用,還假定該函數返回int,并且為該函數生成匯編代碼。此時唯一不確定的,只是該函數的函數地址。這由鏈接器來完成。如:

int main(){ printf("ok/n"); return 0;}

在gcc上會給出隱式函數聲明的警告,但能編譯運行通過。因為在鏈接時,鏈接器在libc中找到了printf符號的定義,并將其地址填到編譯階段留下的空白中。PS:用g++編譯則會生成錯誤:use of undeclared identifier 'printf'。而如果使用的是未經定義的函數,如上面的printf函數改為print,得到的將是鏈接錯誤,而不是編譯錯誤。
2. 頭文件
有了隱式函數聲明,編譯器在編譯時應該就不需要頭文件了,編譯器可以按函數調用時的代碼生成匯編代碼,并且假定函數返回int。而C頭文件的最初目的是用于方便文件之間共享數據結構定義,外部變量,常量宏。早期的頭文件里,也只包含這三樣東西。注意,沒有提到函數聲明。
而如今在引入將函數聲明放入頭文件這一做法后,帶來了哪些便利和缺陷:
優點:
項目不同的文件之間共享接口。
頭文件為第三方庫提供了接口說明。
缺點:
效率性:為了使用一個簡單的庫函數,編譯器可能要parse成千上萬行預處理之后的頭文件源碼。
傳遞性:頭文件具有傳遞性。在頭文件傳遞鏈中任一頭文件變動,都將導致包含該頭文件的所有源文件重新編譯。哪怕改動無關緊要(沒有源文件使用被改動的接口)。
差異性:頭文件在編譯時使用,動態庫在運行時使用,二者有可能因為版本不一致造成二進制兼容問題。
一致性:頭文件函數聲明和源文件函數實現的參數名無需一致。這將可能導致函數聲明的意思,和函數具體實現不一致。如聲明為 void draw(int height, int width) 實現為 void draw(int width, int height)。
3. 單遍編譯( One Pass )
由于當時的編譯器并不能將整個源文件的語法樹保存在內存中,因此編譯器實際上是”單遍編譯”。即編譯器從頭到尾地編譯源文件,一邊解析,一邊即刻生成目標代碼,在單遍編譯時,編譯器只能看到已經解析過的部分。 意味著:
C語言結構體需要先定義,才能訪問。因為編譯器需要知道結構體定義,才知道結構體成員類型和偏移量,并生成目標代碼。
局部變量必須先定義,再使用。編譯器需要知道局部變量的類型和在棧中的位置。
外部變量(全局變量),編譯器只需要知道它的類型和名字,不需要知道它的地址,就能生成目標代碼。而外部變量的地址將留給連接器去填。
對于函數,根據隱式函數聲明,編譯器可以立即生成目標代碼,并假定函數返回int,留下空白函數地址交給連接器去填。
C語言早期的頭文件就是用來提供結構體定義和外部變量聲明的,而外部符號(函數或外部變量)的決議則交給鏈接器去做。
單遍編譯結合隱式函數聲明,將引出一個有趣的例子:

void bar(){ foo('a');}int foo(char a){ printf("foobar/n"); return 0;}int main(){ bar(); return 0;}

gcc編譯上面的代碼,得到如下錯誤:

test.c:16:6: error: conflicting types for 'foo'void foo(char a) ^test.c:12:2: note: previous implicit declaration is here  foo('a');

這是因為當編譯器在bar()中遇到foo調用時,編譯器并不能看到后面近在咫尺的foo函數定義。它只能根據隱式函數聲明,生成int foo(int)的函數調用代碼,注意隱式生成的函數參數為int而不是char,這應該是編譯器做的一個向上轉換,向int靠齊。在編譯器解析到更為適合的int foo(char)時,它可不會認錯,它會認為foo定義和編譯器隱式生成的foo聲明不一致,得到編譯錯誤。將上面的foo函數替換為 void foo(int a)也會得到類似的編譯錯誤,C語言嚴格要求一個符號只能有一種定義,包括函數返回值也要一致。
而將foo定義放于bar之前,就編譯運行OK了。
C++ 編譯模型
到目前為止,我們提到的3點關于C編譯模型的特性,對C語言來說,都是利多于弊的,因為C語言足夠簡單。而當C++試圖兼容這些特性時(C++沒有隱式函數聲明),加之C++本身獨有的重載,類,模板等特性,使得C++更加難以理解。
1. 單遍編譯
C++沒有隱式函數聲明,但它仍然遵循單遍編譯,至少看起來是這樣,單遍編譯語義給C++帶來的影響主要是重載決議和名字解析。
1.1 重載決議

#include<stdio.h>void foo(int a){ printf("foo(int)/n");}void bar(){ foo('a');}void foo(char a){ printf("foo(char)/n");}int main(){ bar(); return 0;}

以上代碼通過g++編譯運行結果為:foo(int)。盡管后面有更合適的函數原型,但C++在解析bar()時,只看到了void foo(int)。
這是C++重載結合單遍編譯造成的困惑之一,即使現在C++并非真的單遍編譯(想一下前向聲明),但它要和C兼容語義,因此不得不”裝傻”。對于C++類是個例外,編譯器會先掃描類的定義,再解析成員函數,因此類中所有同名函數都能參加重載決議。
關于重載還有一點就是C的隱式類型轉換也給重載帶來了麻煩:

// Case 1void f(int){}void f(unsigned int){}void test() { f(5); } // call f(int)// Case 2void f(int){}void f(long){}void test() { f(5); } // call f(int)// Case 3void f(unsigned int){}void f(long){}void test() { f(5); } // error. 編譯器也不知道你要干啥// Case 4void f(unsigned int){}void test{ f(5); } // call f(unsigned int)...void f(long){}

再加上C++子類到父類的隱式轉換,轉換運算符的重載… 你必須費勁心思,才能確保編譯器按你預想的去做。
1.2 名字查找
單遍編譯給C++造成的另一個影響是名字查找,C++只能通過源碼來了解名字的含義,比如 AA BB(CC),這句話即可以是聲明函數,也可以是定義變量。編譯器需要結合它解析過的所有源代碼,來判斷這句話的確切含義。當結合了C++ template之后,這種難度幾何攀升。因此不經意地改動頭文件,或修改頭文件包含順序,都可能改變語句語義和代碼的含義。
2. 頭文件
在初學C++時,函數聲明放在.h文件,函數實現放在.cpp文件,似乎已經成了共識。C++沒有C的隱式函數聲明,也沒有其它高級語言的包機制,因此,同一個項目中,頭文件已經成了模塊與模塊之間,類與類之間,共享接口的主要方式。
C中的效率性,傳遞性,差異性,一致性,C++都一個不落地繼承了。除此之外,C++頭文件還帶來如下麻煩:
2.1 順序性
由于C++頭文件包含更多的內容:template, typedef, #define, #pragma, class,等等,不同的頭文件包含順序,將可能導致完全不同的語義。或者直接導致編譯錯誤。
2.2 又見重載
由于C++支持重載,因此如果頭文件中的函數聲明和源文件中函數實現不一致(如參數個數,const屬性等),將可能構成重載,這個時候”聰明”的C++編譯器不錯報錯,它將該函數的調用地址交給鏈接器去填,而源文件中寫錯了的實現將被認定為一個全新的重載。從而到鏈接階段才報錯。這一點在C中會得到編譯錯誤,因為C沒有重載,也就沒有名字改編(name mangling),將會在編譯時得到符號沖突。
2.3 重復包含
由于頭文件的傳遞性,有可能造成某上層頭文件的重復包含。重復包含的頭文件在展開后,將可能導致符號重定義,如:

// common.hclass Common{ // ...};// h1.h#include "common.h"// h2.h#include "common.h"// test.cpp#include "h1.h"#include "h2.h"int main(){ return 0;}

如果common.h中,有函數定義,結構體定義,類聲明,外部變量定義等等。test.cpp中將展開兩份common.h,編譯時得到符號重定義的錯誤。而如果common.h中只有外部函數聲明,則OK,因為函數可在多處聲明,但只能在一處定義。關于類聲明,C++類保持了C結構體語義,因此叫做”類定義”更為適合。始終記得,頭文件只是一個公共代碼的整合,這些代碼會在預編譯期替換到源文件中。
為了解決重復包含,C++頭文件常用 #ifndef #define #endif或#pragma once來保證頭文件不被重復包含。
2.4 交叉包含
C++中的類出現相互引用時,就會出現交叉包含的情況。如Parent包含一個Child對象,而Child類包含Parent的引用。因此相互包含對方的頭文件,編譯器展開Child.h需要展開Parent.h,展開Parent.h又要展開Child.h,如此無限循環,最終g++給出:error: #include nested too deeply的編譯錯誤。
解決這個問題的方案是前向聲明,在Child類定義前面加上 class Parent; 聲明Parent類,而無需包含其頭文件。前向聲明不止可以用于類,還可以用于函數(即顯式的函數聲明)。前向聲明應該被大量使用,它可以解決頭文件帶來的絕大多數問題,如效率性,傳遞性,重復包含,交叉包含等等。這一點有點像包(package)機制,需要什么,就聲明(導入)什么。前向聲明也有局限:僅當編譯器無需知道目標類完整定義時。如下情形,類A可使用 class B;:
類A中使用B聲明引用或指針;
類A使用B作為函數參數類型或返回類型,而不使用該對象,即無需知道其構造函數和析構函數或成員函數;
2.5 如何使用頭文件
關于頭文件使用的建議:
降低將文件間的編譯依賴(如使用前向聲明);
將頭文件歸類,按照特定順序包含,如C語言系統頭文件,C++系統頭文件,項目基礎頭文件,項目頭文件;
防止頭文件重復編譯(#ifndef or #pragma);
確保頭文件和源文件的一致;
3.總結
C語言本身一些比較簡單的特性,放在C++中卻引起了很多麻煩,主要是因為C++復雜的語言特性:類,模板,各種宏… 舉個例子來說,對于一個類A,它有一個私有函數,需要用到類B,而這個私有函數必須出現在類定義即頭文件中,因此就增加了A頭文件對B的不必要引用。這是因為C++類遵循C結構體的語義,所有類成員都必須出現在類定義中,”屬于這個類的一部分”。這不僅在定義上造成不便,也在容易在語義上造成誤解,事實上,C++類的成員函數不屬于對象,它更像普通函數(虛函數除外)。
而在C中,沒有”類的捆綁”,實現起來就要簡單多了,將該函數放在A.c中,函數不在A.h中聲明。由A.c包含B.h,解除了A.h和B.h之間的關聯,這也是C將數據和操作分離的優勢之一。
最后,看看其它語言是如何避免這些”坑”的:
對于解釋型語言,import的時候直接將對應模塊的源文件解析一遍,而不是將文件包含進來;
對于編譯型語言,編譯后的目標文件中包含了足夠的元數據,不需要讀取源文件(也就沒有頭文件一說了);
它們都避免了定義和聲明不一致的問題,并且在這些語言里面,定義和聲明是一體的。import機制可以確保只到處必要的名字符號,不會有多余的符號加進來。



發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日日狠狠久久偷偷四色综合免费| 亚洲精选一区二区| 日韩精品福利在线| 亚洲国产精久久久久久| 国产亚洲欧洲高清| 亚洲xxxx妇黄裸体| 亚洲**2019国产| 91精品综合久久久久久五月天| 日本久久久久久久久久久| 亚洲欧洲一区二区三区久久| 精品国产一区二区三区久久狼5月| 成人网在线免费观看| 国产精品免费一区二区三区都可以| 久久久精品中文字幕| 亚洲成人精品在线| 久久天堂电影网| 日韩经典一区二区三区| 国产精品欧美激情在线播放| 国产日韩中文字幕| 日韩精品中文字幕有码专区| 国产日韩中文在线| 欧美重口另类videos人妖| 久久香蕉国产线看观看网| 欧美日韩国产中文字幕| 久久99久久久久久久噜噜| 一区二区成人av| 亚洲无亚洲人成网站77777| 亚洲成人免费网站| 精品久久久久久中文字幕大豆网| 伊人一区二区三区久久精品| 中文字幕亚洲综合| 国产激情久久久| 国产精品精品国产| 国产精品2018| 色婷婷久久一区二区| 在线观看国产精品日韩av| 国语自产精品视频在线看一大j8| 日本高清久久天堂| 欧美精品久久久久久久久久| 国产精品女人久久久久久| 精品亚洲一区二区三区| 日韩av免费网站| 国产精品久久久久久av| 欧美日韩免费一区| 欧美电影在线播放| 久久天天躁狠狠躁夜夜躁| 亚洲免费视频网站| 日韩欧美视频一区二区三区| 久久精品电影一区二区| 日韩中文在线中文网三级| 欧美精品激情视频| 欧美大尺度激情区在线播放| 91国自产精品中文字幕亚洲| 97超级碰碰碰久久久| 国产精品69av| 久久久综合免费视频| 欧美三级欧美成人高清www| 国产精品一区二区三区久久| 日韩有码视频在线| 亚洲欧美日韩久久久久久| 亚洲va男人天堂| 性色av一区二区三区红粉影视| 亚洲男人天堂视频| 亚洲黄页网在线观看| 欧美大码xxxx| 国产精选久久久久久| 久久久久久国产精品三级玉女聊斋| 午夜精品一区二区三区在线播放| 久久久久久久一区二区三区| 亚洲色图激情小说| 国产精品成熟老女人| 国语自产在线不卡| 国产精品a久久久久久| 国产精品久久97| 国语自产精品视频在线看一大j8| 欧美视频精品一区| 国产精品 欧美在线| 亚洲精品v欧美精品v日韩精品| 精品国偷自产在线视频| 国产精品视频自拍| 亚洲精品国产成人| 欧洲s码亚洲m码精品一区| 日本一区二区在线播放| 青青草99啪国产免费| 欧美大学生性色视频| 欧美激情在线视频二区| 亚洲国产精品yw在线观看| 久久艳片www.17c.com| 久久的精品视频| 欧美在线激情视频| 成人国产精品久久久久久亚洲| 国产亚洲欧美aaaa| 国产一区二区三区中文| 国产成人精品国内自产拍免费看| 影音先锋欧美精品| 国产午夜精品免费一区二区三区| 午夜剧场成人观在线视频免费观看| 在线播放日韩欧美| 中文字幕综合一区| 日韩av在线最新| 色哟哟网站入口亚洲精品| 国产精品成熟老女人| 国产精品欧美亚洲777777| 成人97在线观看视频| 日韩精品在线观看一区| 久久亚洲一区二区三区四区五区高| 成人精品一区二区三区电影黑人| 国产精品免费久久久久影院| 中文精品99久久国产香蕉| 欧美精品videosex性欧美| 九九热最新视频//这里只有精品| 欧美成人在线免费视频| 亚洲精品免费在线视频| 91欧美精品午夜性色福利在线| 日韩电影大全免费观看2023年上| 正在播放欧美视频| 色综合影院在线| 伊人一区二区三区久久精品| 日韩大胆人体377p| 夜夜嗨av色一区二区不卡| 粉嫩av一区二区三区免费野| 欧美性xxxx| 久久久国产一区| 亚洲视频在线观看网站| 国产在线观看不卡| 国内精品久久久久久中文字幕| 成人疯狂猛交xxx| 午夜精品视频在线| 中文字幕欧美视频在线| 欧美精品18videos性欧| 欧美性生交大片免网| 亚洲乱码av中文一区二区| 亚洲免费视频网站| 国产精品一区二区3区| 亚洲女人天堂av| 久久久精品久久久| 一区二区三区视频免费在线观看| 日韩视频―中文字幕| 成人精品久久一区二区三区| 88国产精品欧美一区二区三区| 亚洲精品国产精品乱码不99按摩| 日韩电影大片中文字幕| 国产脚交av在线一区二区| 久久久www成人免费精品张筱雨| 日韩女优人人人人射在线视频| 欧美黑人xxxx| 国产精品视频精品| 成人欧美在线视频| 欧美一级电影久久| 97精品国产97久久久久久免费| 成人妇女免费播放久久久| 国产精品黄页免费高清在线观看| 久久精品国产精品亚洲| 国产精品视频永久免费播放| 欧美成人免费全部| 亚洲色图25p| 夜夜嗨av色综合久久久综合网| 日韩av快播网址| 国产精品日韩一区| 国产91久久婷婷一区二区| 日本一区二区三区四区视频| 亚洲国产精品电影| 国产精品久久久久国产a级| 国产精品久久久久久久7电影|