C++ 預處理、編譯、彙編、鏈接

一個C++程序一般需要經過以下幾個步驟才能成爲可執行程序:


那麼,在以上的每一步中,編譯器到底做了哪些工作呢?

一、編譯器預處理

C++編譯器自帶預處理器,在程序編譯之前,由預處理器對C++源程序完成預處理工作。

預處理主要將源程序中的宏定義指令、條件編譯指令、頭文件包含指令以及特殊符號完成相應的替換工作。

預處理指令#include用於包含頭文件,有兩種形式:#include <xxx.h>,#include "xxx.h"。
尖括號形式表示被包含的文件在系統目錄中。如果被包含的文件不一定在系統目錄中,應該用雙引號形式。
在雙引號形式中可以指出文件路徑和文件名。如果在雙引號中沒有給出絕對路徑,

則默認爲用戶當前目錄中的文件,此時系統首先在用戶當前目錄中尋找要包含的文件,

若找不到再在系統目錄中查找。對於用戶自己編寫的頭文件,宜用雙引號形式。

對於系統提供的頭文件,既可以用尖括號形式,也可以用雙引號形式,

都能找到被包含的文件,但顯然用尖括號形式更直截了當,效率更高。
./表示當前目錄,../表示當前目錄的父目錄。

stdafx.h

在VS中新建一個WIN32工程時,編譯器一般默認會在主程序中添加一條#include"stdafs.h"語句。

而stdafx.h只包含如下幾行引用頭文件的代碼:

<span style="font-size:18px;">// stdafx.h : 標準系統包含文件的包含文件,
// 或是經常使用但不常更改的
// 特定於項目的包含文件
//

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>



// TODO:  在此處引用程序需要的其他頭文件
</span>
而在stdafx.cpp中,僅有一條語句:

<span style="font-size:18px;">// stdafx.cpp : 只包括標準包含文件的源文件
// defineTest.pch 將作爲預編譯頭
// stdafx.obj 將包含預編譯類型信息

#include "stdafx.h"

// TODO:  在 STDAFX.H 中
// 引用任何所需的附加頭文件,而不是在此文件中引用
</span>

那麼stdafx.h的作用是什麼呢?

所謂預編譯頭,就是把一個工程中要使用的一些標準頭文件預先編譯,以後該工程編譯時,

不再編譯這部分頭文件,僅僅使用預編譯的結果。這樣可以加快編譯速度,節省時間。

關於預編譯頭文件的簡介,百度百科的說法如下:

預編譯頭文件通過編譯stdafx.cpp生成,以工程名命名,由於預編譯的頭文件的後綴是“pch”,
所以編譯結果文件是projectname.pch。
編譯器通過一個頭文件stdafx.h來使用預編譯頭文件。
stdafx.h這個頭文件名是可以在project的編譯設置裏指定的。
編譯器認爲,所有在指令#include "stdafx.h"前的代碼都是預編譯的,
它跳過#include "stdafx. h"指令,使用projectname.pch編譯這條指令之後的所有代碼。
因此,所有的MFC實現文件第一條語句都是:#include "stdafx.h"。在它前面的所有代碼將被忽略,
所以其他的頭文件應該在這一行後面被包含。否則,
你將會得到“No such file or directory”這樣讓你百思不得其解的錯誤提示。

關於第二點,srdafx.h這個頭文件的文件名在VS2013中實在程序->屬性->C/C++->預編譯頭中設置的。

如下圖所示:

而關於stdafx.h的作用:百度百科的說明如下:

stdafx.h中沒有函數庫,只是定義了一些環境參數,使得編譯出來的程序能在32位的操作系統環境下運行。
Windows和MFC的include文件都非常大,即使有一個快速的處理程序,
編譯程序也要花費相當長的時間來完成工作。由於每個.CPP文件都包含相同的include文件,
爲每個.CPP文件都重複處理這些文件就顯得很傻了。
爲避免這種浪費,AppWizard和VisualC++編譯程序一起進行工作,如下所示:
◎AppWizard建立了文件stdafx.h,該文件包含了所有當前工程文件需要的MFCinclude文件。
且這一文件可以隨被選擇的選項而變化。
◎AppWizard然後就建立Stdafx.cpp。這個文件通常都是一樣的。
◎然後AppWizard就建立起工程文件,這樣第一個被編譯的文件就是stdafx.cpp。
◎當VisualC++編譯stdafx.cpp文件時,它將結果保存在一個名爲stdafx.pch的文件裏。
(擴展名pch表示預編譯頭文件。)
◎當VisualC++編譯隨後的每個.cpp文件時,它閱讀並使用它剛生成的.pch文件。
VisualC++不再分析Windows include文件,除非你又編輯了stdafx.cpp或stdafx.h。
在這個過程中你必須遵守以下規則:
◎你編寫的任何.cpp文件都必須首先包含stdafx.h。
◎如果你有工程文件裏的大多數.cpp文件需要.h文件,順便將它們加在stdafx.h(後部)上,然後預編譯stdafx.cpp。
◎由於.pch文件具有大量的符號信息,它是你的工程文件裏最大的文件。
如果你的磁盤空間有限,你就希望能將這個你從沒使用過的工程文件中的.pch文件刪除。
執行程序時並不需要它們,且隨着工程文件的重新建立,它們也自動地重新建立。
經過預處理後的程序,生成一個沒有宏定義、沒有條件編譯指令、沒有特殊符號的輸出文件。
這些輸出文件的含義與沒有經過預處理的源文件是相同的,但內容有所不同。

二、編譯程序。

以預編譯的輸出作爲輸入,利用C++運行庫,通過詞法分析和語法分析,
在確認所有的指令都符合語法規則時,將其翻譯成等價的中間代碼表示或者是彙編語言。

三、優化程序

優化階段一部分是對中間代碼的優化,這種優化不依賴具體的計算機,
同機器的硬件環境無關。另一種優化則主要針對目標代碼的生成而進行的。對於前一種優化,
主要的工作是刪除公共表達式、循環優化(循環展開,自動向量化、循環不變量代碼移動)以及無用賦值的刪除等。
另一種優化同機器的硬件結構密切相關。最主要的是考慮如何充分利用機器的各個硬件寄存器存放有關的變量的值
,以減少對於內存的訪問次數。

四、彙編程序

彙編階段的主要工作是將經過編譯、優化後的,
以彙編語言的形式存在的程序轉化爲機器可識別的二進制代碼,從而得到相應的目標文件。
目標文件通常由數據段和代碼段組成。代碼段保存的是程序的指令,該段一般是可讀和可執行的,
但一般是不可寫的。數據段主要用來保存程序中所定義的或者需要用到的全局變量、
靜態數據(局部變量是在運行階段生成)。數據段一般是可讀可寫的。

五、鏈接程序

經歷了彙編之後的程序是後綴爲.obj形式的文件,仍然是不可執行的,只有經過鏈接階段,
將程序所引用的外部文件關聯起來之後,形成.exe後綴的文件之後,纔是可執行的。
程序中可能引用了定義在其他外部文件中的變量或者函數,比如某些庫函數,
而鏈接階段所做的主要事情就是將這些相關聯的文件鏈接起來,
使得所有這些目標文件成爲一個能夠被操作系統裝入執行的統一的整體。

鏈接方式具體可分爲動態鏈接與靜態鏈接兩種。

動態鏈接:採用該鏈接方式表明,需要鏈接的代碼是存放在動態鏈接庫或者某個共享對象的目標文件中,
鏈接程序(Link.exe)此時所做的只是在最終的可執行程序中記錄下共享對象的名字和其他少量的登記信息,
不會想需要鏈接的代碼拷貝到最終的可執行程序中,在此可執行文件被執行時,
動態鏈接庫的全部內容將被映射到運行時相應進程的虛地址空間,
動態鏈接程序將根據可執行程序中登記的信息找到相應的函數代碼。
採用動態鏈接方式最終生成的可執行程序較小,因爲不會將動態庫中的內容拷貝到可執行程序中。
但需要注意的是,可執行程序在運行時需要目標主機上存在相應的動態庫和環境。

靜態鏈接:採用該鏈接方式,需要鏈接的代碼會被鏈接程序從相應的靜態鏈接庫中拷貝到可執行程序之中,
在可執行程序運行時,這些代碼會裝入到該進程相應的地址空間之中。因此,採用靜態鏈接庫的方式,
最終生成的可執行文件相對較大。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章