導航:首頁 > 源碼編譯 > 編譯器編譯工作執行順序

編譯器編譯工作執行順序

發布時間:2022-11-12 03:00:18

1. 編譯為什麼要分階段

編譯過程分為分析和綜合兩個部分,並進一步劃分為詞法分析、語法分析、 語義分析、 代碼優化、存儲分配和代碼生成等六個相繼的邏輯步驟。

這六個步驟只表示編譯程序各部分之間的邏輯聯系,而不是時間關系。編譯過程既可以按照這六個邏輯步驟順序地執行,也可以按照平行互鎖方式去執行。在確定編譯程序的具體結構時,常常分若干遍實現。對於源程序或中間語言程序,從頭到尾掃視一次並實現所規定的工作稱作一遍。

編譯程序

也稱為編譯器,是指把用高級程序設計語言書寫的源程序,翻譯成等價的機器語言格式目標程序的翻譯程序。編譯程序屬於採用生成性實現途徑實現的翻譯程序。它以高級程序設計語言書寫的源程序作為輸入,而以匯編語言或機器語言表示的目標程序作為輸出。編譯出的目標程序通常還要經歷運行階段,以便在運行程序的支持下運行,加工初始數據,算出所需的計算結果。

2. C語言程序的運行順序

1、這個涉及到函數的調用約定
運行結果跟編譯器有一定的關系,不同的編譯器參數的入棧的順序不同
一般的編譯器是從右到左
如fun(a,b)這個函數調用,是先計算參數b,入棧,再計算參數a,入棧
2、printf("%d
%d",
a++,++a);
//先計算++a,先自增,a的值變為2,將2入棧
再來計算a++,將a的值2入棧,再使a自增,a的值變為3
printf("
%d\n",a);
//a的值已經變為3了
3、printf(%d
%d",
++a.a++);//先計算a++,將a的值1入棧,再使a自增,a的值變為2,再來計算++a,先自增,a的值為3,將3入棧,輸出3
1
printf("
%d\n",a);
//輸出3
4、三種調用約定:
__stdcall調用約定。兩者實質上是一致的,即函數的參數自右向左通過棧傳遞,被調用的函數在返回前清理傳送參數的內存棧,但不同的是函數名的修飾部分(關於函數名的修飾部分在後面將詳細說明)。
C調用約定(即用__cdecl關鍵字說明)和__stdcall調用約定有所不同,雖然參數傳送方面是一樣的,但對於傳送參數的內存棧卻是由調用者來維護的(也正因為如此,實現可變參數的函數只能使用該調用約定),另外,在函數名修飾約定方面也有所不同。
__fastcall調用約定是「人」如其名,它的主要特點就是快,因為它是通過寄存器來傳送參數的(實際上,它用CX和EDX傳送前兩個雙字或更小的參數,剩下的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的內存棧),在函數名修飾約定方面,它和前兩者均不同。

3. 編譯程序的工作過程一般可以劃分為哪5個基本階段,還自始至終伴隨進行哪兩項工作

1、編譯程序把一個源程序翻譯成目標程序的工作過程分為五個階段:詞法分析;語法分析;中間代碼生成;代碼優化;目標代碼生成。

2、編譯程序的工作過程一般自始至終伴隨進行信息表管理和出錯處理兩項工作。

主要是進行詞法分析和語法分析,又稱為源程序分析,分析過程中發現有語法錯誤,給出提示信息。

(3)編譯器編譯工作執行順序擴展閱讀:

解釋程序是一種語言處理程序,在詞法、語法和語義分析方面與編譯程序的工作原理基本相同,但在運行用戶程序時,它直接執行源程序或源程序的內部形式(中間代碼)。因此,解釋程序並不產生目標程序,這是它和編譯程序的主要區別。解釋程序的工作過程如下:

1、由總控程序完成初始化工作。

2、依次從源程序中取出一條語句進行語法檢查,如有錯,輸出錯誤信息;如果通過了語法檢查,則根據語句翻澤成相應的指令並執行它。

3、檢查源程序是否已經全部解釋執行完畢,如果未完成則繼續解釋並執行下一條語句,直到全部語句都處理完畢。

4. C語言程序的執行過程是什麼

順序執行,從main函數開始,順序執行。
遇到調用的其它函數就先運行函數,然後繼續執行主函數下面的語句。
當整個程序編寫好後,編譯器先編譯,再連接各種庫函數,然後執行程序。

5. 【C語言】優先順序,結合方向和執行順序

K&R的書中一再強調"C is not a big language",當時看書的時候無法理解這句話的意思。現在我的理解是C標准本身的限制比較小,留給程序員的空間較大。這樣一來C中的而有些問題標准就沒有給出限定,就會產生一些讓人迷惑的地方。今天我遇到了一個這樣的問題,解決這些問題有的時候看起來是「鑽牛角尖」,好吧,我就是一個愛鑽牛角尖的人。這里需要解決的問題是執行順序的問題,先給出幾個問題。

執行完這兩個語句後i和j的值各是什麼?(看著是不是很熟悉,上次筆試考C中是不是有這個呢)

執行完上述語句後,i和數組arry中的值是多少?

輸出結果是什麼?(來自於C Puzzle Book Operators 1.6 )
先把這幾個問題放在這里,先思考下,下面將會解決他們。

C語言經典著作 「The C Programming Language」 中對於side effects的定義:

這里述說的side effect可以理解為一種「副作用」,這種作用是改變一個變數的值。
「C In Nutshell」 中關於side effects的定義:

相對於K&R中的定義這里使用了對於環境的改變,這應該更加准確。總結:side effects 就是程序中的實體產生的改變,這里所說的實體通常指變數。
賦值,自增,自減表達式會產生side effects,函數調用表達式也有可能產生side effects。

序列點(sequence points)是一種邏輯意義的點,它的意義在於,邏輯點前的副作用(side effects)都在這時生效。C標准中定義的序列點總共有三類,第一類是完全表達式(full expression);第二類是||,&&,?:和;第三類是函數調用,在所有的參數確定後、函數真正調用之前。

標准中規定了在前一個序列點前的副作用都會在前一個序列點後完成,但是標准沒有規定兩個序列點之間的副作用生效的順序,不同的C語言實現的順序可能不同。請注意這一點,這是所有問題產生的根本原因。如果兩個序列點之間有超過兩個的副作用作用在同一個實體上,這樣不同的編譯器產生的結果就不同,這種情況在標准中稱為unspecified 。所以在實際應用中應該避免這種情況的出現,我把這一個原則稱為為SS1。

是不是遵守了SS1原則就不會產生unspecified了呢?非也。可以設想這樣一種情況:每一個實體(A)在兩個序列點之間被兩次使用,只有一次對這個實體本身產生副作用,另外一次被間接的用來產生副作用作用於另外一個實體(B)。在前面設想的這種情況下雖然符合SS1原則,但是我們會發現被間接用來產生副作用時,對於實體(B)產生的副作用肯定會跟實體(A)有關,但是這個實體(A)在這個序列點區間中有被副作用作用,那麼我們就無法確定這個實體(A)的值了,從而實體(B)也就無法確定了。這里可以歸納為:在兩個序列點之間,如果出現對一個實體的多次引用,並且只有一次會對該實體產生副作用(SS1),那麼所有的這些引用都必須用來產生這個副作用 ,我把這一個原則稱為SS2。只有同時遵守了SS1和SS2,寫出的表達式才不是unspecified類型的。

可以清晰的看到標准中使用了兩句話來概括這種問題,這正好對應於SS1,SS2原則。

下面給出更為具體的方法:
1)在一個表達式中最多隻改變一個實體。
2)如果一個實體在一個表達式被改變,並且出現次數大於一次,請保證所有實體的出現都是為了產生這個「改變」。
例如: i = i+1;
3)如果不能遵守1),那麼請保證改變的是不同的實體。
例如:c = *p++;
4)如果1)和2)都不能遵守,那麼請使用序列點將表達式分開。
例如 : (c = getchar()) != EOF && c != 『\n』;

C語言中組成程序的基本單位是表達式(expression),表達式是指用操作符(operator)和操作數(operand)連接起來的式子。C標准給出了最基本的操作符,通過這些操作符可以組成簡單表達式,同樣也可以通過復合產生復雜表達式。當一個表達式中出現多個操作符,多個操作數的時候,操作符合操作數是如何組合起來的呢?優先順序和結合方向就是用來解決這個問題的,可以這么說,優先順序和結合方向給出了一個表達式的含義,這只是說明了各個操作符和操作數是怎麼聚合起來的。

僅僅依靠優先順序和結合方向是無法確定一個復合表達式中對各個子表達式的求值順序。標准中對於這點的規定是:
兩個相鄰的操作符的執行順序由它們的優先順序決定。如果它們優先順序相同,它們的執行順序由它們的結合性決定。除此之外,編譯器可以自由決定任何順序對表達式進行求值,只要它不違反逗號,&&,||和?:操作符所施加的限制。

1)j = i++ + i++ + i++;
這個表達式違反了SS1,不同的編譯器產生的結果可能不同。
2)arry[i] = i++;
這個表達式違反了SS2,不同的編譯器產生的結果可能不同。
3)x = y = z = 1;
++x || ++y && ++z; PRINT(x, y, z);
&&的優先順序比|| 的優先順序高,所以:
++x || ++y && ++z 等效於++x || (++y && ++z)
這里就很容易犯錯,會認為先執行++y && ++z 在執行++x || ( … ),這種觀點是錯誤的,c中只對 逗號,、邏輯與 &&、
邏輯或 || 和 條件表達式規定了執行順序,對於邏輯表達式方向是從左向右執行的。本例子中,先執行 ++x = 2,邏輯
或表達式被短路,++y && ++z沒有執行,最後x = 2, y = 1, z = 1;

通過本課題的學習加深了對於c的理解,理解SS1,SS2原則。在實際應用中應該遵守「一條語句只做一件事的原則」。

6. C語言文件的編譯與執行的四個階段並分別描述

開發C程序有四個步驟:編輯、編譯、連接和運行。

任何一個體系結構處理器上都可以使用C語言程序,只要該體系結構處理器有相應的C語言編譯器和庫,那麼C源代碼就可以編譯並連接到目標二進制文件上運行。

1、預處理:導入源程序並保存(C文件)。

2、編譯:將源程序轉換為目標文件(Obj文件)。

3、鏈接:將目標文件生成為可執行文件(EXE文件)。

4、運行:執行,獲取運行結果的EXE文件。

(6)編譯器編譯工作執行順序擴展閱讀:

將C語言代碼分為程序的幾個階段:

1、首先,源代碼文件測試。以及相關的頭文件,比如stdio。H、由預處理器CPP預處理為.I文件。預編譯的。文件不包含任何宏定義,因為所有宏都已展開,並且包含的文件已插入。我歸檔。

2、編譯過程是對預處理文件進行詞法分析、語法分析、語義分析和優化,生成相應的匯編代碼文件。這個過程往往是整個程序的核心部分,也是最復雜的部分之一。

3、匯編程序不直接輸出可執行文件,而是輸出目標文件。匯編程序可以調用LD來生成可以運行的可執行程序。也就是說,您需要鏈接大量的文件才能獲得「a.out」,即最終的可執行文件。

4、在鏈接過程中,需要重新調整其他目標文件中定義的函數調用指令,而其他目標文件中定義的變數也存在同樣的問題。

7. c++編譯器以何種順序編譯文件的,先cpp文件,還是.h文件

1. 編譯階段 (頭文件 .h)
d工程中在頭文件中對導出內容(function, class, type, object, variable)進行定義.

2. 鏈接階段 (庫文件 .lib)
d工程在link階段會生成.lib
用戶link時需要 這個.lib 解決link時的代碼定位.
3. 運行階段
.exe

8. 編譯器生成的匯編語句執行順序為什麼與C代碼順序不同

不影響語義的前提下編譯器可以任意重排代碼順序;
在亂序執行(Out-of-Order)的CPU里,機器碼的執行也可以不按照你在「匯編」層面上看到的順序執行,只要不影響語義。
所以說這些中間步驟的順序,作為底層細節平時不需要那麼在意——它們多半跟原始源碼的順序是不一樣的。

現代優化編譯器優化的思路之一是「基於依賴的優化」(dependence-based optimization)。題主引用的CSAPP的例子:

int arith(int x, int y, int z) {
int t1 = x + y;
int t2 = z * 48;
int t3 = t1 & 0xFFFF;
int t4 = t2 * t3;
return t4;
}

所有涉及運算的值都是局部標量變數(local scalar variable),這是最便於編譯器做分析的情況,所有依賴都可以顯式分析。
由於整個函數沒有分支,這里也不需要討論控制依賴(control dependence),只要討論數據依賴(data dependence)就好。
把數據依賴圖畫出來是個DAG(這里正好是棵樹,特例了):

x y z 48
\ / \ /
t1 0xFFFF t2
\ / /
t3 /
\ /
t4

優化必須要滿足的約束是:每個節點求值之前,其子節點(依賴的數據源)必須要先求了值。
顯然,t1和t2之間沒有依賴關系,它們的相對求值順序怎樣重排都沒關系。

有本我很喜歡的書,裡面講的是各種基於依賴的優化:Optimizing Compilers for Modern Architectures - A Dependence-based Approach

以上是理論部分。

================================================================

下面來看例子。

我們可以用一個實際編譯器來看看CSAPP的例子編譯出來的結果:

.text
# -- Begin arith
.p2align 4,,15
.globl arith
.type arith, @function
arith:
.p2align 4,,7
/*.L0:*/ /* Block BB[54:2] preds: none, freq: 1.000 */
movl 8(%esp), %edx /* ia32_Load T[139:10] -:1:22 */
addl 4(%esp), %edx /* ia32_Add Iu[141:12] -:2:14 */
movzwl %dx, %edx /* ia32_Conv_I2I Iu[142:13] -:4:15 */
imull 12(%esp), %edx /* ia32_IMul Iu[143:14] -:5:15 */
leal (%edx,%edx,2), %eax /* ia32_Lea Iu[144:15] -:5:15 */
shll $0x4, %eax /* ia32_Shl Iu[146:17] -:5:15 */
ret /* ia32_Return X[152:23] -:6:3 */
.size arith, .-arith
# -- End arith

這里用的是libFirm。可見它跟CSAPP書里所說的匯編的順序又有所不同。這也是完全合理的。
這個編譯結果的順序是:

edx = y;
edx += x;
edx = zeroextend dx; // edx = edx & 0xFFFF
edx *= z;
eax = edx * 3;
eax <<= 4; // eax = eax * 16

也是完全符合依賴關系的約束的一種順序。
之所以用libFirm舉例是因為它的中間表示(Intermediate Representation)是一種程序依賴圖(Program Dependence Graph),可以很方便的看出控制與數據依賴。把CSAPP那裡例子對應的libFirm IR畫出來,是這個樣子的:
(這張圖跟我前面畫的數據依賴圖正好是左右翻轉的,不過意思一樣。(這張圖跟我前面畫的數據依賴圖正好是左右翻轉的,不過意思一樣。
Arg 0、1、2分別代表x、y、z。白色方塊是普通數據節點,黃色方塊是常量節點,藍色方塊是內存相關節點,紅色方塊是控制流節點,粉紅色方塊是特殊的開始/結束節點。)

某版LLVM生成的代碼:

; MoleID = '/tmp/webcompile/_16355_0.bc'
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-ellcc-linux"

; Function Attrs: nounwind readnone
define i32 @arith(i32 %x, i32 %y, i32 %z) #0 {
entry:
%add = add nsw i32 %y, %x
%mul = mul nsw i32 %z, 48
%and = and i32 %add, 65535
%mul1 = mul nsw i32 %mul, %and
ret i32 %mul1
}

attributes #0 = { nounwind readnone "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.ident = !{!0}

!0 = !{!"ecc 0.1.10 based on clang version 3.7.0 (trunk) (based on LLVM 3.7.0svn)"}

最終生成的x86匯編:

.text
.file "/tmp/webcompile/_15964_0.c"
.globl arith
.align 16, 0x90
.type arith,@function
arith: # @arith
# BB#0: # %entry
movl 8(%esp), %eax
addl 4(%esp), %eax
movzwl %ax, %eax
imull 12(%esp), %eax
shll $4, %eax
leal (%eax,%eax,2), %eax
retl
.Ltmp0:
.size arith, .Ltmp0-arith

.ident "ecc 0.1.10 based on clang version 3.7.0 (trunk) (based on LLVM 3.7.0svn)"
.section ".note.GNU-stack","",@progbits

GCC 4.9.2 x86-64:

arith(int, int, int):
leal (%rdx,%rdx,2), %eax
addl %edi, %esi
movzwl %si, %esi
sall $4, %eax
imull %esi, %eax
ret

Zing VM Server Compiler x86-64:

# edi: x
# esi: y
# edx: z
movl %edx, %eax
shll $0x4, %eax
leal (%rsi, %rdi, 1), %ecx
shll $0x5, %edx
addl %edx, $eax
movzwl %ecx, %edx
imull %edx, %eax

9. 簡述java程序的編輯編譯和運行過程

第一步(編譯): 創建完源文件之後,程序會先被編譯為.class文件。Java編譯一個類時,如果這個類所依賴的類還沒有被編譯,編譯器就會先編譯這個被依賴的類,然後引用,否則直接引用,這個有點象make。

如果java編譯器在指定目錄下找不到該類所其依賴的類的.class文件或者.java源文件的話,編譯器話報「cant find symbol」的錯誤。

第二步(運行):java類運行的過程大概可分為兩個過程:1、類的載入 2、類的執行。需要說明的是:JVM主要在程序第一次主動使用類的時候,才會去載入該類。也就是說,JVM並不是在一開始就把一個程序就所有的類都載入到內存中,而是到不得不用的時候才把它載入進來,而且只載入一次。

特別說明:java類中所有public和protected的實例方法都採用動態綁定機制,所有私有方法、靜態方法、構造器及初始化方法<clinit>都是採用靜態綁定機制。而使用動態綁定機制的時候會用到方法表,靜態綁定時並不會用到。

(9)編譯器編譯工作執行順序擴展閱讀:

Java整個編譯以及運行的過程相當繁瑣,本文通過一個簡單的程序來簡單的說明整個流程。

Java代碼編譯:是由Java源碼編譯器來完成;

Java位元組碼的執行:是由JVM執行引擎來完成

Java程序從源文件創建到程序運行要經過兩大步驟:

1、源文件由編譯器編譯成位元組碼(ByteCode)

2、位元組碼由java虛擬機解釋運行。因為java程序既要編譯同時也要經過JVM的解釋運行,所以說Java被稱為半解釋語言( "semi-interpreted" language)。

10. 編譯器的工作分為哪幾個階段

編譯器就是一個普通程序,沒什麼大不了的
什麼是編譯器?

編譯器是一個將高級語言翻譯為低級語言的程序。

首先我們一定要意識到編譯器就是一個普通程序,沒什麼大不了的。

在沒有弄明白編譯器如何工作之前你可以簡單的把編譯器當做一個黑盒子,其作用就是輸入一個文本文件輸出一個二進制文件。

基本上編譯器經過了以下幾個階段,等等,這句話教科書上也有,但是我相信很多同學其實並沒有真正理解這幾個步驟到底在說些什麼,為了讓你徹底理解這幾個步驟,我們用一個簡單的例子來講解。

假定我們有一段程序:

while (y < z) {
int x = a + b;
y += x;
}
那麼編譯器是怎樣把這一段程序人類認識的程序轉換為CPU認識的二進制機器指令呢?

提取出每一個單詞:詞法分析
首先編譯器要把源代碼中的每個「單詞」提取出來,在編譯技術中「單詞」被稱為token。其實不只是每個單詞被稱為一個token,除去單詞之外的比如左括弧、右括弧、賦值操作符等都被稱為token。

從源代碼中提取出token的過程就被稱為詞法分析,Lexical Analysis。

經過一遍詞法分析,編譯器得到了以下token:

T_While while
T_LeftParen (
T_Identifier y
T_Less <
T_Identifier z
T_RightParen )
T_OpenBrace {
T_Int int
T_Identifier x
T_Assign =
T_Identifier a
T_Plus +
T_Identifier b
T_Semicolon ;
T_Identifier y
T_PlusAssign +=
T_Identifier x
T_Semicolon ;
T_CloseBrace }
就這樣一個磁碟中保存的字元串源代碼文件就轉換為了一個個的token。

這些token想表達什麼意思:語法分析
有了這些token之後編譯器就可以根據語言定義的語法恢復其原本的結構,怎麼恢復呢?

原來,編譯器在掃描出各個token後根據規則將其用樹的形式表示出來,這顆樹就被稱為語法樹。

語法樹是不是合理的:語義分析
有了語法樹後我們還要檢查這棵樹是不是合法的,比如我們不能把一個整數和一個字元串相加、比較符左右兩邊的數據類型要相同,等等。

這一步通過後就證明了程序合法,不會有編譯錯誤。

閱讀全文

與編譯器編譯工作執行順序相關的資料

熱點內容
php函數的返回值嗎 瀏覽:584
國企穩定程序員 瀏覽:325
編程貓如何使用教程視頻 瀏覽:216
安卓遠端網頁如何打日誌 瀏覽:216
壓縮flash大小 瀏覽:991
解壓的玩具教程可愛版 瀏覽:366
哪個求職app比較靠譜 瀏覽:888
java的讀法 瀏覽:59
nod32區域網伺服器地址 瀏覽:1002
數碼科技解壓 瀏覽:235
新網的雲伺服器管理界面復雜嗎 瀏覽:367
無人聲解壓強迫症視頻 瀏覽:571
計算機編譯運行 瀏覽:639
單片機嵌套 瀏覽:988
python字元串中符號 瀏覽:787
python正則表達式貪婪模式 瀏覽:649
愛國精神指的是什麼app 瀏覽:408
壽司解壓系列全集視頻 瀏覽:913
物體三維重建演算法 瀏覽:984
fuli直播app哪個好 瀏覽:918