1. 程式人生 > >c語言編譯過程詳解,預處理,編譯,彙編,連結(乾貨滿滿)

c語言編譯過程詳解,預處理,編譯,彙編,連結(乾貨滿滿)

鍥子

我們在各自的電腦上寫下程式碼,得明白我們程式碼究竟是如何產生的,不想了解1,0什麼的,但這幾個環節必須掌握吧。

我們的程式碼會經過這4個環節,從而形成最終檔案,c語言作為編譯語言,用來向計算機發出指令。讓程式設計師能夠準確地定義計算機所需要使用的資料,並精確地定義在不同情況下所應當採取的行動。

預處理, 展開標頭檔案/巨集替換/去掉註釋/條件編譯                      (test.i main .i)
編譯,    檢查語法,生成彙編                                                      ( test.s  main .s)
彙編,   彙編程式碼轉換機器碼                                                         (test.o main.o)
連結     連結到一起生成可執行程式                                              a.out
 

預處理

預處理如鍥子中所言,是一種展開,下表是常用的一些預處理命令

還有下列幾種預處理巨集(是雙下劃線)

__LINE__ 表示正在編譯的檔案的行號
__FILE__表示正在編譯的檔案的名字__DATE__表示編譯時刻的日期字串,例如: "25 Dec 2007"
__TIME__ 表示編譯時刻的時間字串,例如: "12:30:55"
__STDC__ 判斷該檔案是不是定義成標準 C 程式
我的vs2013不是定義的標準c語言

巨集函式很好用,是直接展開,在這我順便說一下巨集的好處和壞處。

巨集優點1程式碼複用性2提高效能

巨集缺點1 不可除錯(預編譯階段進行了替換),2無型別安全檢查3可讀性差,容易出錯。

這裡附上《c和指標》中的一張表格,總結巨集和函式十分到位,我就不多說了

巨集函式很皮,#define定義一個比如判斷大小,替換常量,很是方便。

不過我現在也就用下,#define ERROR_POWEROFF -1,#define _CRT_SECURE_NO_WARNINGS 1這樣的和編譯器有關的東西,不會去寫巨集函式,巨集函式這東西,可讀性特別差,在c++中,一般用const/列舉/內聯去替代巨集。

但是,define巨集在某些方面真的是非常好用,我很推薦。

1.替代路徑

#define ENG_PATH_1 C:\Program Files (x86)

2.針對編譯器版本不相容報錯

#define _CRT_SECURE_NO_WARNINGS 1

3.條件編譯

#ifdef 識別符號
程式段 1
#else
程式段 2
#endif

4.使用庫中的巨集

vc++中有許多有意思的巨集,都是大牛們寫出來的,真的是充滿智慧,十分刁鑽,怎麼學也學不完,我個人擔心出錯就很少寫巨集,用函式代替了。在以後的部落格中我會記錄一些常用的,充作筆記。

emmm,當然,還有其他許多重要的預處理。

比如

include

#include <filename>

尖括號是預處理到系統規定的路徑中去獲得這個檔案(即 C 編譯系統所提供的並存放在指定的子目錄下的標頭檔案)。找到檔案後,用檔案內容替換該語句。如stdio.h

#include“filename”

“”則是預處理我們自己第三方的檔案,如程式設計師小劉寫的Date.h,我們就可以include“Date.h”

#error 預處理,#line 預處理,#pragma 預處理

#error 預處理指令的作用是,編譯程式時,只要遇到 #error 就會生成一個編譯錯誤提示訊息,並停止編譯。

這個我沒寫過,但碰到過很多次,在編寫mfc程式碼中,拉入控制元件時我加入密碼框控制元件,OS編譯時會自動彈出#error 提示我該編輯框為密碼,注意明文問題

#line 的作用是改變當前行數和檔名稱,如#line 28  liu 

目前我沒使其派上用場,但瞭解為好。

#pragma 是比較重要且困難的預處理指令。

#pragma once 

這個的做用就是防止標頭檔案多次包含

當然,還有另外一種風格,防止被包含,我同時給出來

是巧妙地利用了define巨集

#ifndef _SOME_H
#define _SOME_H


...//(some.h標頭檔案內容)


#endif

變數的防止重複定義則利用extern,在標頭檔案中不初始化只宣告。引用該標頭檔案即可,在連結過程中。就可以使用到這個變數。

(附:extern在c++中經常用於  extern "C"  告訴編譯器下面是c語言風格)

#pragma warning

#pragma warning( disable : 4507 34; once : 4385; error : 164 )
等價於:
#pragma warning(disable:4507 34) // 不顯示 4507 和 34 號警告資訊
#pragma warning(once:4385) // 4385 號警告資訊僅報告一次
#pragma warning(error:164) // 把 164 號警告資訊作為一個錯誤。

另外還有

#pragma pack

使用指令#pragma pack (n),編譯器將按照 n 個位元組對齊。
使用指令#pragma pack (),編譯器將取消自定義位元組對齊方式。
在#pragma pack (n)和#pragma pack ()之間的程式碼按 n 個位元組對齊。

位元組對齊,我將另起爐灶,在另外一篇部落格中歸納總結。

#pragma pack(push) //儲存當前對其方式到 packing stack
#pragma pack(push,n) 等效於
#pragma pack(push)
#pragma pack(n) //n=1,2,4,8,16 儲存當前對齊方式,設定按 n 位元組對齊
#pragma pack(pop) //packing stack 出棧,並將對其方式設定為出棧的對齊

#運算子和##預算符

#define SQR(x) printf("The square of "#x" is %d.\n", ((x)*(x)));

這段程式碼中#就是幫助x作為一個變數,表現出來,而不是一個簡單的字母

如果有#,SQR(3)運算出來就是

The square of 3  is 9

如果沒有# SQL(3)運算出來就是

The square of x  is 9

##預算符

##把兩個語言符號組合成單個語言符號

編譯

編譯階段是檢查語法,生成彙編,這個屬於程式設計師的必備知識,我們學習一門語言第一步就是知曉語法,其中比較生澀的有左值右值,指標的使用,記憶體的管理,資料結構的使用,這將會是一場持久戰 ,貫穿在整個學習生涯。

在這裡我擷取優先順序問題,這個可能會通過編譯但是不一定達到程式設計師想要的結果。

在這裡,我引用《c語言深度解剖》中的一張表格

彙編

  彙編程式碼轉換機器碼   這個階段,非底層的程式設計師不需要考慮, 編譯器不會搞錯的。也與c/c++開發者無關,但是我們可以利用反彙編來除錯程式碼,學習組合語言依然是必備的。

連結

開頭我引用一下百度百科的介紹

靜態連結是由連結器在連結時將庫的內容加入到可執行程式中的做法。連結器是一個獨立程式,將一個或多個庫或目標檔案(先前由編譯器彙編器生成)連結到一塊生成可執行程式。靜態連結是指把要呼叫的函式或者過程連結到可執行檔案中,成為可執行檔案的一部分。

動態連結所呼叫的函式程式碼並沒有被拷貝到應用程式的可執行檔案中去,而是僅僅在其中加入了所呼叫函式的描述資訊(往往是一些重定位資訊)。僅當應用程式被裝入記憶體開始執行時,在Windows的管理下,才在應用程式與相應的DLL之間建立連結關係。當要執行所呼叫DLL中的函式時,根據連結產生的重定位資訊,Windows才轉去執行DLL中相應的函式程式碼。

將原始檔中用到的庫函式與彙編生成的目標檔案.o合併生成可執行檔案。該可執行檔案會變大很多,一般是呼叫自己電腦上的。

靜態庫和應用程式編譯在一起,在任何情況下都能執行,而動態庫是動態連結,檔案生效時才會呼叫。

很多程式碼編譯通過,連結失敗就極有可能在靜態庫和動態庫這出現了紕漏,要視情況解決。缺少相關所需檔案,就會連結報錯。這個時候就要檢查下本地的連結庫是不是缺損。