C C++的编译过程详解
生活随笔
收集整理的這篇文章主要介紹了
C C++的编译过程详解
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
C/C++編譯過程
C/C++編譯過程主要分為4個過程
1) 編譯預(yù)處理
2) 編譯、優(yōu)化階段
3) 匯編過程
4) 鏈接程序
一、編譯預(yù)處理
(1)宏定義指令,如#define Name TokenString,#undef等。 對于前一個偽指令,預(yù)編譯所要做的是將程序中的所有Name用TokenString替換,
但作為字符串常量的 Name則不被替換。對于后者,則將取消對某個宏的定義,使以后該串的出現(xiàn)不再被替換。
(2)條件編譯指令,如#ifdef,#ifndef,#else,#elif,#endif等。 這些偽指令的引入使得程序員可以通過定義不同的宏來決定編譯程序?qū)δ男┐a進(jìn)行處理。
預(yù)編譯程序?qū)⒏鶕?jù)有關(guān)的文件,將那些不必要的代碼過濾掉
(3) 頭文件包含指令,如#include "FileName"或者#include <FileName>等。 在頭文件中一般用偽指令#define定義了大量的宏(最常見的是字符常量),
同時包含有各種外部符號的聲明。 包含到c源程序中的頭文件可以是系統(tǒng)提供的,這些頭文件一般被放在/usr/include目錄下。
在程序中#include它們要使用尖括號(< >)。
另外開發(fā)人員也可以定義自己的頭文件,這些文件一般與c源程序放在同一目錄下,此時在#include中要用雙引號("")。
(4)特殊符號,預(yù)編譯程序可以識別一些特殊的符號。 例如在源程序中出現(xiàn)的#line標(biāo)識將被解釋為當(dāng)前行號(十進(jìn)制數(shù)),
上面程序?qū)崿F(xiàn)了對宏line的運用
(5)預(yù)處理模塊 預(yù)處理工作由#pragma命令完成,#Pragma命令將設(shè)定編譯器的狀態(tài)或者是指示編譯器完成一些特定的動作。
#pragma指令對每個編譯器給出了一個方法,在保持與C和C++語言完全兼容的情況下,給出主機或操作系統(tǒng)專有的特征。
依據(jù)定義,編譯指示是機器或操作系統(tǒng)專有的,且對于每個編譯器都是不同的。
打開C標(biāo)準(zhǔn)庫函數(shù),如stdio.h,我們總能找到下面這一句指示編譯器初始化堆棧
#include "iostream"
#line 100
using namespace std;
int main(int argc, char* argv[])
{
cout<<"__LINE__:"<<__LINE__<<endl;
return 0;
}
/*--------------------
* 輸出結(jié)果為:
* __LINE__:103
* 本來輸出的結(jié)果應(yīng)該是 7,但是用#line指定行號之后,使下一行的行號變?yōu)?
* 到輸出語句恰為行103
---------------------*/
C/C++編譯過程
或者程序指示編譯器去鏈接系統(tǒng)動態(tài)鏈接庫或用戶自定義鏈接庫
二、編譯、優(yōu)化階段
經(jīng)過預(yù)編譯得到的輸出文件中,只有常量;如數(shù)字、字符串、變量的定義,以及C語言的關(guān)鍵字,如main,if,else,for,while,{,}, +,-,*,\等等。
在《編譯原理》中我們可以了解到一個編譯器對程序代碼的編譯主要分為下面幾個過程:
a) 詞法分析
b) 語法分析
c) 語義分析
d) 中間代碼生成
e) 代碼優(yōu)化
f) 代碼生成
g) 符號表管理
h) 將多個步驟組合成趟
i) 編譯器構(gòu)造工具
在這里我們主要強調(diào)對函數(shù)壓棧方式(函數(shù)調(diào)用約定)的編譯處理
C與C++語言調(diào)用方式大體相同,下面是幾種常用的調(diào)用方式:
__cdecl 是C DECLaration的縮寫(declaration,聲明),表示C語言默認(rèn)的函數(shù)調(diào)用方法:所有參數(shù)從右到左依次入棧,
這些參數(shù)由調(diào)用者清除,稱為手動清棧。被調(diào)用函數(shù)不需要求調(diào)用者傳遞多少參數(shù),調(diào)用者傳遞過多或者過少的參數(shù),
甚至完全不同的參數(shù)都不會產(chǎn)生編譯階段的錯誤。
_stdcall 是StandardCall的縮寫,是C++的標(biāo)準(zhǔn)調(diào)用方式:所有參數(shù)從右到左依次入棧,如果是調(diào)用類成員的話,
最后一個入棧的是this指針。這些堆棧中的參數(shù)由被調(diào)用的函數(shù)在返回后清除,使用的指令是 retnX,X表示參數(shù)占用的字節(jié)數(shù),
CPU在ret之后自動彈出X個字節(jié)的堆??臻g。稱為自動清棧。函數(shù)在編譯的時候就必須確定參數(shù)個數(shù),
并且調(diào)用者必須嚴(yán)格的控制參數(shù)的生成,不能多,不能少,否則返回后會出錯。
PASCAL 是Pascal語言的函數(shù)調(diào)用方式,在早期的c/c++語言中使用這種調(diào)用方式,
參數(shù)壓棧順序與前兩者相反,但現(xiàn)在我們在程序中見到的都是它的演化版本,其實
#pragma comment(lib,_T("GDI32.lib"))
#ifdef _MSC_VER
/*
* Currently, all MS C compilers for Win32 platforms default to 8 byte
* alignment.
*/
#pragma pack(push,_CRT_PACKING)
#endif /* _MSC_VER */
C/C++編譯過程
質(zhì)是另一種調(diào)用方式
_fastcall是編譯器指定的快速調(diào)用方式。由于大多數(shù)的函數(shù)參數(shù)個數(shù)很少,使用堆棧傳遞比較費時。因此_fastcall通常規(guī)定將前兩個(或若干個)參數(shù)由寄存器傳遞,其余參數(shù)還是通過堆棧傳遞。不同編譯器編譯的程序規(guī)定的寄存器不同。返回方式和_stdcall相當(dāng)。
_thiscall 是為了解決類成員調(diào)用中this指針傳遞而規(guī)定的。_thiscall要求把this指針放在特定寄存器中,該寄存器由編譯器決定。VC使用ecx,Borland的C++編譯器使用eax。返回方式和_stdcall相當(dāng)。
_fastcall 和 _thiscall涉及的寄存器由編譯器決定,因此不能用作跨編譯器的接口。所以Windows上的COM對象接口都定義為_stdcall調(diào)用方式。
C中不加說明默認(rèn)函數(shù)為_cdecl方式(C中也只能用這種方式),C++也一樣,但是默認(rèn)的調(diào)用方式可以在IDE環(huán)境中設(shè)置。簡單的我們可以從printf函數(shù)看出
printf使用從從左至右壓棧,返回int型并由_CRTIMP指定封在動態(tài)鏈接庫中。
通過金典的hello world程序我們可以知道編譯器對其argc和argv[]這兩個參數(shù)進(jìn)行了壓棧,并且argc留在了棧頂
優(yōu)化處理是編譯系統(tǒng)中一項比較艱深的技術(shù)。它涉及到的問題不僅同編譯技術(shù)本身有關(guān),而且同機器的硬件環(huán)境也有很大的關(guān)系。優(yōu)化處理主要分為下面幾個過程:
1) 局部優(yōu)化
a) 基本塊的劃分
b) 基本塊的變換
c) 基本塊的DAG表示
d) DAG的應(yīng)用
e) 構(gòu)造算法討論
2) 控制流分析和循環(huán)優(yōu)化
a) 程序流圖與循環(huán)
/*金典的hello world*/
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("hello world");
return 0;
}
_Check_return_opt_ _CRTIMP int __cdecl printf(_In_z_ _Printf_format_string_ const char * _Format, ...);
#define CALLBACK _stdcall /* Windows程序回調(diào)函數(shù)*/
#define WINAPI _stdcall
#define WINAPIV _cdecl
#define PASCAL _stdcall /*在c++語言中使用了StandardCall調(diào)用方式*/
#define PASCAL _cdecl/*在c語言中使用了C DECLaration調(diào)用方式*/
C/C++編譯過程
b) 循環(huán)
c) 循環(huán)的查找
d) 可歸約流圖
e) 循環(huán)優(yōu)化
3) 數(shù)據(jù)流的分析與全局優(yōu)化
a) 一些主要的概念
b) 數(shù)據(jù)流方程的一般形式
c) 到達(dá)一定值數(shù)據(jù)流方程
d) 可用表達(dá)式及其數(shù)據(jù)流方程
e) 活躍變量數(shù)據(jù)流方程
f) 復(fù)寫傳播
經(jīng)過優(yōu)化得到的匯編代碼必須經(jīng)過匯編程序的匯編轉(zhuǎn)換成相應(yīng)的機器指令,方可能被機器執(zhí)行。
三、匯編過程
匯編過程實際上指把匯編語言代碼翻譯成目標(biāo)機器指令的過程。對于被翻譯系統(tǒng)處理的每一個C語言源程序,
都將最終經(jīng)過這一處理而得到相應(yīng)的目標(biāo)文件。目標(biāo)文件中所存放的也就是與源程序等效的目標(biāo)的機器語言代碼。
目標(biāo)文件由段組成。通常一個目標(biāo)文件中至少有兩個段: 代碼段:該段中所包含的主要是程序的指令。
該段一般是可讀和可執(zhí)行的,但一般卻不可寫。 數(shù)據(jù)段:主要存放程序中要用到的各種全局變量或靜態(tài)的數(shù)據(jù)。一般數(shù)據(jù)段都是可讀,可寫,可執(zhí)行的。
四、鏈接程序
由匯編程序生成的目標(biāo)文件并不能立即就被執(zhí)行,其中可能還有許多沒有解決的問題。
例如,某個源文件中的函數(shù)可能引用了另一個源文件中定義的某個符號(如變量或者函數(shù)調(diào)用等);
在程序中可能調(diào)用了某個庫文件中的函數(shù),等等。所有的這些問題,都需要經(jīng)鏈接程序的處理方能得以解決。
鏈接程序的主要工作就是將有關(guān)的目標(biāo)文件彼此相連接,也即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,
使得所有的這些目標(biāo)文件成為一個能夠誒操作系統(tǒng)裝入執(zhí)行的統(tǒng)一整體。
根據(jù)開發(fā)人員指定的同庫函數(shù)的鏈接方式的不同,鏈接處理可分為兩種:
(1)靜態(tài)鏈接 在這種鏈接方式下,函數(shù)的代碼將從其所在地靜態(tài)鏈接庫中被拷貝到最終的可執(zhí)行程序中。
這樣該程序在被執(zhí)行時這些代碼將被裝入到該進(jìn)程的虛擬地址空間中。靜態(tài)鏈接庫實際上是一個目標(biāo)文件的集合,
其中的每個文件含有庫中的一個或者一組相關(guān)函數(shù)的代碼。
(2) 動態(tài)鏈接
在此種方式下,函數(shù)的代碼被放到稱作是動態(tài)鏈接庫或共享對象的某個目標(biāo)文件中。
鏈接程序此時所作的只是在最終的可執(zhí)行程序中記錄下共享對象的名字以及其它少量
的登記信息。在此可執(zhí)行文件被執(zhí)行時,動態(tài)鏈接庫的全部內(nèi)容將被映射到運行時相應(yīng)
進(jìn)程的虛地址空間。動態(tài)鏈接程序?qū)⒏鶕?jù)可執(zhí)行程序中記錄的信息找到相應(yīng)的函數(shù)代碼。
C/C++編譯過程
對于可執(zhí)行文件中的函數(shù)調(diào)用,可分別采用動態(tài)鏈接或靜態(tài)鏈接的方法。使用動
態(tài)鏈接能夠使最終的可執(zhí)行文件比較短小,并且當(dāng)共享對象被多個進(jìn)程使用時能節(jié)約一
些內(nèi)存,因為在內(nèi)存中只需要保存一份此共享對象的代碼。但并不是使用動態(tài)鏈接就一
定比使用靜態(tài)鏈接要優(yōu)越。在某些情況下動態(tài)鏈接可能帶來一些性能上損害。
-----------------------------------------------------------------------------------------------------------------------------------作者? 張彥升
C/C++編譯過程主要分為4個過程
1) 編譯預(yù)處理
2) 編譯、優(yōu)化階段
3) 匯編過程
4) 鏈接程序
一、編譯預(yù)處理
(1)宏定義指令,如#define Name TokenString,#undef等。 對于前一個偽指令,預(yù)編譯所要做的是將程序中的所有Name用TokenString替換,
但作為字符串常量的 Name則不被替換。對于后者,則將取消對某個宏的定義,使以后該串的出現(xiàn)不再被替換。
(2)條件編譯指令,如#ifdef,#ifndef,#else,#elif,#endif等。 這些偽指令的引入使得程序員可以通過定義不同的宏來決定編譯程序?qū)δ男┐a進(jìn)行處理。
預(yù)編譯程序?qū)⒏鶕?jù)有關(guān)的文件,將那些不必要的代碼過濾掉
(3) 頭文件包含指令,如#include "FileName"或者#include <FileName>等。 在頭文件中一般用偽指令#define定義了大量的宏(最常見的是字符常量),
同時包含有各種外部符號的聲明。 包含到c源程序中的頭文件可以是系統(tǒng)提供的,這些頭文件一般被放在/usr/include目錄下。
在程序中#include它們要使用尖括號(< >)。
另外開發(fā)人員也可以定義自己的頭文件,這些文件一般與c源程序放在同一目錄下,此時在#include中要用雙引號("")。
(4)特殊符號,預(yù)編譯程序可以識別一些特殊的符號。 例如在源程序中出現(xiàn)的#line標(biāo)識將被解釋為當(dāng)前行號(十進(jìn)制數(shù)),
上面程序?qū)崿F(xiàn)了對宏line的運用
(5)預(yù)處理模塊 預(yù)處理工作由#pragma命令完成,#Pragma命令將設(shè)定編譯器的狀態(tài)或者是指示編譯器完成一些特定的動作。
#pragma指令對每個編譯器給出了一個方法,在保持與C和C++語言完全兼容的情況下,給出主機或操作系統(tǒng)專有的特征。
依據(jù)定義,編譯指示是機器或操作系統(tǒng)專有的,且對于每個編譯器都是不同的。
打開C標(biāo)準(zhǔn)庫函數(shù),如stdio.h,我們總能找到下面這一句指示編譯器初始化堆棧
#include "iostream"
#line 100
using namespace std;
int main(int argc, char* argv[])
{
cout<<"__LINE__:"<<__LINE__<<endl;
return 0;
}
/*--------------------
* 輸出結(jié)果為:
* __LINE__:103
* 本來輸出的結(jié)果應(yīng)該是 7,但是用#line指定行號之后,使下一行的行號變?yōu)?
* 到輸出語句恰為行103
---------------------*/
C/C++編譯過程
或者程序指示編譯器去鏈接系統(tǒng)動態(tài)鏈接庫或用戶自定義鏈接庫
二、編譯、優(yōu)化階段
經(jīng)過預(yù)編譯得到的輸出文件中,只有常量;如數(shù)字、字符串、變量的定義,以及C語言的關(guān)鍵字,如main,if,else,for,while,{,}, +,-,*,\等等。
在《編譯原理》中我們可以了解到一個編譯器對程序代碼的編譯主要分為下面幾個過程:
a) 詞法分析
b) 語法分析
c) 語義分析
d) 中間代碼生成
e) 代碼優(yōu)化
f) 代碼生成
g) 符號表管理
h) 將多個步驟組合成趟
i) 編譯器構(gòu)造工具
在這里我們主要強調(diào)對函數(shù)壓棧方式(函數(shù)調(diào)用約定)的編譯處理
C與C++語言調(diào)用方式大體相同,下面是幾種常用的調(diào)用方式:
__cdecl 是C DECLaration的縮寫(declaration,聲明),表示C語言默認(rèn)的函數(shù)調(diào)用方法:所有參數(shù)從右到左依次入棧,
這些參數(shù)由調(diào)用者清除,稱為手動清棧。被調(diào)用函數(shù)不需要求調(diào)用者傳遞多少參數(shù),調(diào)用者傳遞過多或者過少的參數(shù),
甚至完全不同的參數(shù)都不會產(chǎn)生編譯階段的錯誤。
_stdcall 是StandardCall的縮寫,是C++的標(biāo)準(zhǔn)調(diào)用方式:所有參數(shù)從右到左依次入棧,如果是調(diào)用類成員的話,
最后一個入棧的是this指針。這些堆棧中的參數(shù)由被調(diào)用的函數(shù)在返回后清除,使用的指令是 retnX,X表示參數(shù)占用的字節(jié)數(shù),
CPU在ret之后自動彈出X個字節(jié)的堆??臻g。稱為自動清棧。函數(shù)在編譯的時候就必須確定參數(shù)個數(shù),
并且調(diào)用者必須嚴(yán)格的控制參數(shù)的生成,不能多,不能少,否則返回后會出錯。
PASCAL 是Pascal語言的函數(shù)調(diào)用方式,在早期的c/c++語言中使用這種調(diào)用方式,
參數(shù)壓棧順序與前兩者相反,但現(xiàn)在我們在程序中見到的都是它的演化版本,其實
#pragma comment(lib,_T("GDI32.lib"))
#ifdef _MSC_VER
/*
* Currently, all MS C compilers for Win32 platforms default to 8 byte
* alignment.
*/
#pragma pack(push,_CRT_PACKING)
#endif /* _MSC_VER */
C/C++編譯過程
質(zhì)是另一種調(diào)用方式
_fastcall是編譯器指定的快速調(diào)用方式。由于大多數(shù)的函數(shù)參數(shù)個數(shù)很少,使用堆棧傳遞比較費時。因此_fastcall通常規(guī)定將前兩個(或若干個)參數(shù)由寄存器傳遞,其余參數(shù)還是通過堆棧傳遞。不同編譯器編譯的程序規(guī)定的寄存器不同。返回方式和_stdcall相當(dāng)。
_thiscall 是為了解決類成員調(diào)用中this指針傳遞而規(guī)定的。_thiscall要求把this指針放在特定寄存器中,該寄存器由編譯器決定。VC使用ecx,Borland的C++編譯器使用eax。返回方式和_stdcall相當(dāng)。
_fastcall 和 _thiscall涉及的寄存器由編譯器決定,因此不能用作跨編譯器的接口。所以Windows上的COM對象接口都定義為_stdcall調(diào)用方式。
C中不加說明默認(rèn)函數(shù)為_cdecl方式(C中也只能用這種方式),C++也一樣,但是默認(rèn)的調(diào)用方式可以在IDE環(huán)境中設(shè)置。簡單的我們可以從printf函數(shù)看出
printf使用從從左至右壓棧,返回int型并由_CRTIMP指定封在動態(tài)鏈接庫中。
通過金典的hello world程序我們可以知道編譯器對其argc和argv[]這兩個參數(shù)進(jìn)行了壓棧,并且argc留在了棧頂
優(yōu)化處理是編譯系統(tǒng)中一項比較艱深的技術(shù)。它涉及到的問題不僅同編譯技術(shù)本身有關(guān),而且同機器的硬件環(huán)境也有很大的關(guān)系。優(yōu)化處理主要分為下面幾個過程:
1) 局部優(yōu)化
a) 基本塊的劃分
b) 基本塊的變換
c) 基本塊的DAG表示
d) DAG的應(yīng)用
e) 構(gòu)造算法討論
2) 控制流分析和循環(huán)優(yōu)化
a) 程序流圖與循環(huán)
/*金典的hello world*/
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("hello world");
return 0;
}
_Check_return_opt_ _CRTIMP int __cdecl printf(_In_z_ _Printf_format_string_ const char * _Format, ...);
#define CALLBACK _stdcall /* Windows程序回調(diào)函數(shù)*/
#define WINAPI _stdcall
#define WINAPIV _cdecl
#define PASCAL _stdcall /*在c++語言中使用了StandardCall調(diào)用方式*/
#define PASCAL _cdecl/*在c語言中使用了C DECLaration調(diào)用方式*/
C/C++編譯過程
b) 循環(huán)
c) 循環(huán)的查找
d) 可歸約流圖
e) 循環(huán)優(yōu)化
3) 數(shù)據(jù)流的分析與全局優(yōu)化
a) 一些主要的概念
b) 數(shù)據(jù)流方程的一般形式
c) 到達(dá)一定值數(shù)據(jù)流方程
d) 可用表達(dá)式及其數(shù)據(jù)流方程
e) 活躍變量數(shù)據(jù)流方程
f) 復(fù)寫傳播
經(jīng)過優(yōu)化得到的匯編代碼必須經(jīng)過匯編程序的匯編轉(zhuǎn)換成相應(yīng)的機器指令,方可能被機器執(zhí)行。
三、匯編過程
匯編過程實際上指把匯編語言代碼翻譯成目標(biāo)機器指令的過程。對于被翻譯系統(tǒng)處理的每一個C語言源程序,
都將最終經(jīng)過這一處理而得到相應(yīng)的目標(biāo)文件。目標(biāo)文件中所存放的也就是與源程序等效的目標(biāo)的機器語言代碼。
目標(biāo)文件由段組成。通常一個目標(biāo)文件中至少有兩個段: 代碼段:該段中所包含的主要是程序的指令。
該段一般是可讀和可執(zhí)行的,但一般卻不可寫。 數(shù)據(jù)段:主要存放程序中要用到的各種全局變量或靜態(tài)的數(shù)據(jù)。一般數(shù)據(jù)段都是可讀,可寫,可執(zhí)行的。
四、鏈接程序
由匯編程序生成的目標(biāo)文件并不能立即就被執(zhí)行,其中可能還有許多沒有解決的問題。
例如,某個源文件中的函數(shù)可能引用了另一個源文件中定義的某個符號(如變量或者函數(shù)調(diào)用等);
在程序中可能調(diào)用了某個庫文件中的函數(shù),等等。所有的這些問題,都需要經(jīng)鏈接程序的處理方能得以解決。
鏈接程序的主要工作就是將有關(guān)的目標(biāo)文件彼此相連接,也即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,
使得所有的這些目標(biāo)文件成為一個能夠誒操作系統(tǒng)裝入執(zhí)行的統(tǒng)一整體。
根據(jù)開發(fā)人員指定的同庫函數(shù)的鏈接方式的不同,鏈接處理可分為兩種:
(1)靜態(tài)鏈接 在這種鏈接方式下,函數(shù)的代碼將從其所在地靜態(tài)鏈接庫中被拷貝到最終的可執(zhí)行程序中。
這樣該程序在被執(zhí)行時這些代碼將被裝入到該進(jìn)程的虛擬地址空間中。靜態(tài)鏈接庫實際上是一個目標(biāo)文件的集合,
其中的每個文件含有庫中的一個或者一組相關(guān)函數(shù)的代碼。
(2) 動態(tài)鏈接
在此種方式下,函數(shù)的代碼被放到稱作是動態(tài)鏈接庫或共享對象的某個目標(biāo)文件中。
鏈接程序此時所作的只是在最終的可執(zhí)行程序中記錄下共享對象的名字以及其它少量
的登記信息。在此可執(zhí)行文件被執(zhí)行時,動態(tài)鏈接庫的全部內(nèi)容將被映射到運行時相應(yīng)
進(jìn)程的虛地址空間。動態(tài)鏈接程序?qū)⒏鶕?jù)可執(zhí)行程序中記錄的信息找到相應(yīng)的函數(shù)代碼。
C/C++編譯過程
對于可執(zhí)行文件中的函數(shù)調(diào)用,可分別采用動態(tài)鏈接或靜態(tài)鏈接的方法。使用動
態(tài)鏈接能夠使最終的可執(zhí)行文件比較短小,并且當(dāng)共享對象被多個進(jìn)程使用時能節(jié)約一
些內(nèi)存,因為在內(nèi)存中只需要保存一份此共享對象的代碼。但并不是使用動態(tài)鏈接就一
定比使用靜態(tài)鏈接要優(yōu)越。在某些情況下動態(tài)鏈接可能帶來一些性能上損害。
-----------------------------------------------------------------------------------------------------------------------------------作者? 張彥升
轉(zhuǎn)載于:https://www.cnblogs.com/ainima/p/6331152.html
總結(jié)
以上是生活随笔為你收集整理的C C++的编译过程详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于visual Studio2013解
- 下一篇: 基于visual Studio2013解