預編譯編譯匯編鏈接
『壹』 c語言 四個過程:預處理,編譯,匯編,鏈接,分別進行了什麼過程別度娘。
預處理:替換代碼中的預處理命令(宏定義就是在這里直接替換的)
編譯:對代碼按執行順序進行編譯成.o或.obj目標文件
匯編:將其他高級語言轉換成機器語言
鏈接:代碼中的各種調用關系重定位
『貳』 預處理器做了什麼2.編譯器做了什麼3.匯編器做了什麼4.鏈接器做了什麼
1.預處理首先源代碼文件(.c/.cpp)和相關頭文件(.h/.hpp)被預處理器cpp預編譯成.i文件(C++為.ii)。預處理命令為:gcc–Ehello.c–ohello.i預編譯過程主要處理那些源代碼中以#開始的預編譯指令,主要處理規則如下:u將所有的#define
『叄』 gcc編譯線程程序,為什麼要加-lpthread,頭文件已經包含了<pthread.h>了啊
-lpthread是鏈接庫,
<pthread.h>只有申明,實現部分都在庫裡面。
創建線程時一般是把函數的指針做參數,所以要加一個取地址符號。
ret=pthread_create(&id,NULL,(void *)&thread,NULL);
另外,建議要檢查一下創建線程的返回值ret是否成功,防止影響後面的代碼。
(3)預編譯編譯匯編鏈接擴展閱讀:
每個語言編譯器都是獨立程序,此程序可處理輸入的原始碼,並輸出組合語言碼。全部的語言編譯器都擁有共通的中介架構:一個前端解析符合此語言的原始碼,並產生一抽象語法樹,以及一翻譯此語法樹成為GCC的暫存器轉換語言〈RTL〉的後端。
編譯器最佳化與靜態程序碼解析技術(例如FORTIFY_SOURCE,一個試圖發現緩沖區溢位〈buffer overflow〉的編譯器)在此階段應用於程序碼上。最後,適用於此硬體架構的組合語言程序碼以Jack Davidson與Chris Fraser發明的演算法產出。
『肆』 cmake 怎麼配置才能生成預編譯文件和匯編文件
CMake是一個比make更高級的編譯配置工具,它可以根據不同平台、不同的編譯器,生成相應的Makefile或者vcproj項目。 通過編寫CMakeLists.txt,可以控制生成的Makefile,從而控制編譯過程。CMake自動生成的Makefile不僅可以通過make命令構建項目生...
『伍』 完成 C 源文件編輯後、到生成執行文件,C 語言處理系統必須執行的步驟
D是正確答案呀!因為,c語言中沒有匯編這一選項,只有預編譯、編譯、連接、運行呀!
順序也是對的,所以選擇D是正確的!
————————————————————————————————Mr Computer
『陸』 從預處理、編譯、匯編到鏈接,編譯系統都作了哪些工作使用哪些工具生成了哪些文件
這個問題可煩可簡,可深可淺。
對於編譯執行語言而言:
我所知的籠統過程有
(1)源代碼==》目標代碼==》可執行程序
(資源==》目標代碼)
(2)源代碼==》中間代碼==》目標代碼==》可執行程序
第(1)種一般的為低級匯編採用的模式,第一個主要步驟統稱為Assembly(匯編),由「匯編程序」(或稱匯編編譯器)完成,其包含預處理操作,生成的主要文件是目標文件,當然在生成目的文件前還有許多輔助文件,一般會被「匯編程序」臨時生成,用完即刪除,不指定控制選項的話最終用戶是看不到這些文件的,有哪些中間臨時文件,用處是什麼可以查看「匯編編譯器」的幫助選項得到。第二個主要步驟就是link(鏈接),其將目標代碼文件,鏈接庫里的目標代碼塊整合為可執行代碼,中間也臨時生成一些中間文件,如映射文件等,同樣可通過鏈接器的選項查看。
當然,在一些高級匯編里還會有資源編譯器,其將各種資源轉為(編譯為)目標文件(作為鏈接器的輸入)
第(2)種一般是高級語言採用的模式,但有些比較高級的直接跳過中間代碼由源代碼生成目標代碼,其就跟(1)類似,只是此時第一個主要步驟不叫「匯編」而稱compile(編譯),低級匯編的步驟一「匯編」也可稱」編譯「。如果有中間代碼生成,這中間代碼就是匯編代碼,此後續處理就同(1)了,此時的中間代碼其實也就是臨時文件中的一種。
概述:源代碼到目標代碼的過程通常稱為編譯,而目標代碼到可執行程序的過程稱問鏈接。
或將兩個過程統稱為代碼的編譯(全稱應為編譯連接),這涉及具體的語境,事實上編譯器如VC的cl.exe若沒有指定/c(只生產目標代碼選項),其就是編譯連接的統一過程(cl會調用相應的鏈接器),若指定,則只有編譯過程(只生成目標代碼而不鏈接稱可執行程序)
上述編譯執行類語言開發平台所開發生成的程序一般稱為」非託管類程序「
而對於託管類程序(如.NET平台語言C#,VB.NET,JVM平台的java等)
其雖然也有編譯過程,但其直接將源代碼轉為中間代碼而不是目標代碼(此時不是匯編代碼更不是機器碼,而是可被.NET或JVM引擎解釋執行的代碼)
可參看編譯原理等相關教材,阿門。。。
『柒』 8.應用C語言進行ARM 系統軟體開發時從預處理、編譯、匯編到鏈接,編譯系統要做哪些工作,生成哪些文件
預編譯將.c
文件轉化成
.i文件使用的gcc命令是:gcc
–E對應於預處理命令cpp編譯將.c/.h文件轉換成.s文件使用的gcc命令是:gcc
–S對應於編譯命令
cc
–S匯編將.s
文件轉化成
.o文件使用的gcc
命令是:gcc
–c對應於匯編命令是
as鏈接將.o文件轉化成可執行程序使用的gcc
命令是:
gcc對應於鏈接命令是
ld總結起來編譯過程就上面的四個過程:預編譯、編譯、匯編、鏈接。
『捌』 在開發一個裸機程序時,有多個點c的文件,在做編譯鏈接時,怎麼鏈接
一個工程就是就是一個C程序,工程雖然可以包含多個程序文件,但不可以編譯多個C程序。編譯器是在編譯階段分別編譯工程內的多個文件,最後將編譯各個文件得到的多個obj目標文件鏈接到一起成為一個可執行程序。因此無論這個工程包含多少源代碼文件,只有一個文件可以定義main函數。
『玖』 簡述將源程序編譯成可執行程序的過程
一個源程序到一個可執行程序的過程:預編譯、編譯、匯編、鏈接。其中,編譯是主要部分,其中又分為六個部分:詞法分析、語法分析、語義分析、中間代碼生成、目標代碼生成和優化。
預編譯:主要處理源代碼文件中的以「#」開頭的預編譯指令。處理規則如下:
1、刪除所有的#define,展開所有的宏定義。
2、處理所有的條件預編譯指令,如「#if」、「#endif」、「#ifdef」、「#elif」和「#else」。
3、處理「#include」預編譯指令,將文件內容替換到它的位置,這個過程是遞歸進行的,文件中包含其他文件。
4、刪除所有的注釋,「//」和「/**/」。
5、保留所有的#pragma 編譯器指令,編譯器需要用到他們,如:#pragma once 是為了防止有文件被重復引用。
6、添加行號和文件標識,便於編譯時編譯器產生調試用的行號信息,和編譯時產生編譯錯誤或警告是能夠顯示行號。
(9)預編譯編譯匯編鏈接擴展閱讀:
編譯過程中語法分析器只是完成了對表達式語法層面的分析,語義分析器則對表達式是否有意義進行判斷,其分析的語義是靜態語義——在編譯期能分期的語義,相對應的動態語義是在運行期才能確定的語義。
其中,靜態語義通常包括:聲明和類型的匹配,類型的轉換,那麼語義分析就會對這些方面進行檢查,例如將一個int型賦值給int*型時,語義分析程序會發現這個類型不匹配,編譯器就會報錯。
『拾』 C文件如何成為可執行文件(編譯、鏈接、執行)——摘自《程序員的自我修養》
本文算是我閱讀《程序員的自我修養》(俞甲子等著)相關章節的筆記,文中直接引用了原書中的敘述,強烈建議大家去看原書,本文只做概要介紹而用。——註:文中有很多引用圖的地方,請大家自己去找原書看,支持正版!我遇到一個問題,Linux C編程中的問題:.. char *p; unsigned int i = 0xcccccccc; unsigned int j; p = (char *) &i; printf("%.2x %.2x %.2x %.2x\n", *p, p[1], p[2], p[3]); memcpy(&j, p, sizeof(unsigned int)); printf("%x\n", j); ... Output: ffffffcc ffffffcc ffffffcc ffffffcc 0xcccccccc My questions are: 1. Why it prints "ffffffcc ffffffcc ffffffcc ffffffcc"? (if p is unsigned char* then it will print correctly "cc cc cc cc") 2. Why pointer to char p copied to j correctly, why not every member in p overflow? since it is a signed char. 這是別人在郵件列表中提出的問題,在試圖回答這個問題的過程中,突然發現,自己對連接器的工作並不熟悉,因此拿來好書《程序員的自我修養》來看,並做如下匯報,強烈推薦《程序員的自我修養》!!!寫好的C語言文件,最終能夠執行,大致要經過預處理、編譯、匯編、鏈接、裝載五個過程。預編譯完成的工作: (1)將所有的"#define"刪除,並展開所有的宏定義 (2)處理所有條件預編譯指令 (3)處理#include預編譯指令,將被包含的文件插入到預編譯指令的位置,這個過程是遞歸進行的。 (4)刪除所有的注釋 (5)添加行號和文件名標識,以便調試 (6)保留所有的#pragma編譯器命令,因為編譯器需要使用它們。編譯完成的工作: (1)詞法分析 掃描源代碼序列,並將其分割為一系列的記號(Token)。 (2)語法分析 用語法分析器生成語法樹,確定運算符號的優先順序和含義、報告語法錯誤。 (3)語義分析 靜態語義分析包括生命和類型的匹配,類型的轉換;動態語義分析一般是在運行期出現的與語義相關性的問題,如除0錯。 (4)源代碼生成 源代碼級優化器在源代碼級別進行優化:如將如(6+2)之類的表達式,直接優化為(8)等等。將語法書轉換為中間代碼,如三地址碼、P-代碼等。 (5)代碼生成 將源代碼轉換為目標代碼,依賴於目標機器。 (6)目標代碼優化匯編完成的工作: 將匯編代碼變成機器可以執行的指令鏈接完成的工作: 鏈接完成的工作主要是將各個模塊之間相互引用的部分處理好,使得各個模塊之間正確銜接。鏈接過程包括:地址和空間分配、符號決議和重定位。 首先講靜態鏈接,基本的靜態鏈接如下: 我們可能在main函數中調用到定義在另一個文件中的函數foo(),但是由於每個模塊式單獨編譯的,因此main並不知道foo的地址,所以它暫時把這些調用foo的指令的目標地址擱置,等到最後鏈接的時候讓連接器去修正這些地址(重定位),這就是靜態鏈接最基本的過程和作用;對於定義在其他文件中的變數,也存在相同的問題。具體過程如下: (1)空間和地址分配 1)空間與地址分配:掃描所有輸入目標文件,獲得各個段的屬性、長度和位置,並且將目標文件中的符號表中所有的符號定義和符號引用收集起來,放到一個全局符號表中。 2)符號解析和重定位:使用第一步收集到的信息,讀取輸入文件中段的數據、重定位信息,並進行符號解析與重定位、調整代碼中的地址等。 動態鏈接的過程更為復雜,但是完成的工作類似。 動態鏈接的初衷是為了解決空間浪費和更新困難的問題,把鏈接過程推遲到運行時進行 首先介紹一個重要的概念——地址無關代碼。為了解決固定裝載地址沖突的問題,我們希望對所有絕對地址的引用不作重定位,而把這一步推遲到裝載的時候再完成,一旦模塊裝載地址確定,即目標地址確定,那麼系統對程序中所有的絕對地址引用進行重定位。同時我們希望,模塊中共享的指令部分在裝載時不需要因為裝載地址的改變而改變,所以把指令中那些需要被修改的部分分離出來,跟數據放在一起,這樣指令部分就可以保持不變,而數據部分可以在每個進程中擁有一個副本,這種方案目前被稱為地址無關代碼(PIC,Position-independent Code)。 我們需要解決如下四種引用中的重定位問題: 1)模塊內部調用或者跳轉:這個可以用相對地址調用或者基於寄存器的相對調用,所以不需要重定位2)模塊內部數據的訪問:用相對定址的方法,不過鏈接器實現得十分巧妙: call494 add$0x188c, %ecx mov$0x1, 0x28(%ecx) //a=1 調用一個叫做__i686.get_pc_thunk.cx的函數,把call的下一條指令的地址放到ecx寄存器中,接著執行一條mov指令和一個add指令3)模塊間數據的訪問:在數據段里建立一個指向全局變數的指針數組,也成全局便宜表(GOT),當要引用全局變數時,可以通過GOT相對應的項間接引用: GOT是做到指令無關的重要的一環:在編譯時可以確定GOT相對於當前指令的偏移,根據變數地址在GOT中的偏移就可以得到變數的地址,當然GOT中哪個每個地址對應於哪個變數是由編譯器決定的。4)模塊間的調用、跳轉:採用上面類似的方法,不同的是GOT中相應的項存儲的是目標函數的地址,當模塊需要調用目標函數時,可以通過GOT中的項進行間接跳轉。 地址無關代碼小結: 現在,來看動態鏈接中的另一個重要問題——延遲綁定(PLT)。當函數第一次被用到時才進行綁定,否則不綁定。PLT為了實現延遲綁定,增加了一層間接跳轉。調用函數並不是通過GOT跳轉的,而是通過一個叫PLT項的結構進行跳轉的,每個外部函數在PLT中都有對應的項,如函數bar,其在PLT對應的項的地址記為bar@plt,實現方式如下: bar@plt: jmp* (bar@GOT) pushn pushmoleID jump_dl_runtime_resolve 鏈接器的這個實現至為巧妙: 如果在連接器初始化階段,已經正確的初始化了bar@GOT,那麼這個跳轉指令的結果正是我們所期望的,但是,為了實現PLT,一般在連接器初始化時,將"pushn"的地址放入到bar@GOT中,這樣就直接跳轉到第二條指令,相當於沒有進行任何操作。第二條指令「pushn」,n是bar這個符號引用在重定位表「.rel.plt」中的下標。接著將模塊的ID壓棧,跳轉到_dl_runtime_resolve完成符號解析和重定位工作,然後將bar的地址填入到bar@GOT中。下次再調用到bar時,則bar@GOT中存儲的是一個正確的地址,這樣就完成了整個過程。 在鏈接完成之後,就生成了你要的可執行文件了,如ELF文件,至於這個文件的詳細的信息,可以參考相關的文檔。 現在,你要運行你的可執行文件,這是如何做到的呢? 我們從操作系統的角度來看可執行文件的裝載過程。操作系統主要做如下三件事情:(1)創建一個獨立的虛擬地址空間,但由於採用了COW機制,這里只是復制了父進程的頁目錄和頁表,甚至不設置映射關系(參考操作系統相關書籍)。(2)讀取可執行文件頭,並且建立虛擬空間與可執行文件的映射關系。(3)將CPU的指令寄存器設置成可執行文件的入口地址,啟動運行。我們來看一下執行過程中,進程虛擬空間的分布。 首先我們來區分Section和Segment,都可以翻譯為「段」,那麼有什麼不同呢?從鏈接的角度來講,elf文件是按照Section存儲的,從裝載的角度講,elf文件是按照Segment存儲的。」Segment」實際上是從裝載的角度重新劃分了ELF的各個段,將其中屬性相似的Section合並為一個Segment,而系統是按照Segment來映射可執行文件的。