編譯型語言和解釋型語言
從PHP,Java和C語言的編譯執行過程可以先解釋下編譯型語言和解釋型語言。
-
編譯型語言
程式在執行之前需要一個專門的編譯過程,把程式編譯成為機器語言的檔案,執行時不需要重新翻譯,直接使用編譯的結果就行了。程式執行效率高,依賴編譯器,跨平臺性差些。如C、C++、Delphi等.
-
解釋型語言
程式不需要編譯,程式在執行時才翻譯成機器語言,每執行一次都要翻譯一次。因此效率比較低。比如Basic語言,專門有一個直譯器能夠直接執行Basic程式,每個語句都是執行的時候才翻譯。(在執行程式的時候才翻譯,專門有一個直譯器去進行翻譯,每個語句都是執行的時候才翻譯。效率比較低,依賴直譯器,跨平臺性好.)
PHP語言編譯執行過程
下面都是鳥哥部落格的內容:深入理解PHP原理之opcode
hello.php
<?php
echo "Hello World";
$a = 1 + 1;
echo $a;
?>
Zend引擎對這個hello.php檔案進行詞法分析,語法分析,編譯成opcode,然後執行opcode。這個Zend引擎是安裝PHP時安裝的。看看這個檔案是如何執行的,會經過如下4個階段:
php hello.php
1.Scanning(Lexing) ,將PHP程式碼轉換為語言片段(Tokens)
2.Parsing, 將Tokens轉換成簡單而有意義的表示式
3.Compilation, 將表示式編譯成Opocdes
4.Execution, 順次執行Opcodes,每次一條,從而實現PHP指令碼的功能。
在作業系統中執行php命令也就是執行Zend引擎,然後Zend引擎拿到hello.php檔案
那什麼是Lexing? 學過編譯原理的同學都應該對編譯原理中的詞法分析步驟有所瞭解,Lex就是一個詞法分析的依據表。 Zend/zend_language_scanner.c會根據Zend/zend_language_scanner.l(Lex檔案),來輸入的 PHP程式碼進行詞法分析,從而得到一個一個的“詞”,PHP4.2開始提供了一個函式叫token_get_all,這個函式就可以講一段PHP程式碼 Scanning成Tokens;
如果用這個函式處理我們開頭提到的PHP程式碼,將會得到如下結果:
Array
(
[0] => Array
(
[0] => 367
[1] => Array
(
[0] => 316
[1] => echo
)
[2] => Array
(
[0] => 370
[1] =>
)
[3] => Array
(
[0] => 315
[1] => "Hello World"
)
[4] => ;
[5] => Array
(
[0] => 370
[1] =>
)
[6] => =
[7] => Array
(
[0] => 370
[1] =>
)
[8] => Array
(
[0] => 305
[1] => 1
)
[9] => Array
(
[0] => 370
[1] =>
)
[10] => +
[11] => Array
(
[0] => 370
[1] =>
)
[12] => Array
(
[0] => 305
[1] => 1
)
[13] => ;
[14] => Array
(
[0] => 370
[1] =>
)
[15] => Array
(
[0] => 316
[1] => echo
)
[16] => Array
(
[0] => 370
[1] =>
)
[17] => ;
)
分析這個返回結果我們可以發現,原始碼中的字串,字元,空格,都會原樣返回。每個原始碼中的字元,都會出現在相應的順序處。而,其他的比如標籤,操作符,語句,都會被轉換成一個包含倆部分的Array: Token ID (也就是在Zend內部的改Token的對應碼,比如,T_ECHO,T_STRING),和原始碼中的原來的內容。
接下來,就是Parsing階段了,Parsing首先會丟棄Tokens Array中的多餘的空格,然後將剩餘的Tokens轉換成一個一個的簡單的表示式
> 1.echo a constant string
> 2.add two numbers together
> 3.store the result of the prior expression to a variable
> 4.echo a variable
1.Opcode數字的標識,指明瞭每個op_array的操作型別,比如add , echo
2.結果 存放Opcode結果
3.運算元1 給Opcode的運算元
4.運算元2
5.擴充套件值 1個整形用來區別被過載的操作符
然後就改Compilation階段了,它會把Tokens編譯成一個個op_array, 每個op_array包含如下5個部分
其中opcode數字識別符號對應zend_vm_opcode.h中的指令
參考laruence:opcode列表
比如,我們的PHP程式碼會被Parsing成:
* ZEND_ECHO `Hello World`
* ZEND_ADD ~0 1 1
* ZEND_ASSIGN !0 ~0
* ZEND_ECHO !0
Java語言編譯執行過程
JVM執行程式的過程 :
I.載入.class檔案
II.管理並分配記憶體
III.執行垃圾收集
JRE(java執行時環境)包含JVM的java程式的執行環境 [1]
JVM是Java程式執行的容器,但是他同時也是作業系統的一個程式,因此他也有他自己的執行的生命週期,也有自己的程式碼和資料空間。
JVM在整個jdk中處於最底層,負責與作業系統的互動,用來遮蔽作業系統環境,提供一個完整的Java執行環境,因此也就虛擬計算機.作業系統裝入JVM是通過jdk中Java.exe來完成,通過下面4步來完成JVM環境。
1.建立JVM裝載環境和配置
2.裝載JVM.dll
3.初始化JVM.dll並掛接到JNIENV(JNI呼叫介面)例項
4.呼叫JNIEnv例項裝載並處理class類。
C語言編譯執行過程
參考原文:C語言編譯過程詳解
平時開發中,大家可能一行程式碼就編譯好了原始碼,如下:
$ gcc hello.c # 編譯
$ ./a.out # 執行
hello world!
這個過程如此熟悉,以至於大家覺得編譯事件很簡單的事。事實真的如此嗎?我們來細看一下C語言的編譯過程到底是怎樣的。
上述gcc命令其實依次執行了四步操作:
1.預處理(Preprocessing)
2.編譯(Compilation)
3.彙編(Assemble)
4.連結(Linking)
示例程式碼:
// test.c
#include <stdio.h>
#include "mymath.h"// 自定義標頭檔案
int main(){
int a = 2;
int b = 3;
int sum = add(a, b);
printf("a=%d, b=%d, a+b=%d
", a, b, sum);
}
標頭檔案定義:
// mymath.h
#ifndef MYMATH_H
#define MYMATH_H
int add(int a, int b);
int sum(int a, int b);
#endif
標頭檔案實現:
// mymath.c
int add(int a, int b){
return a+b;
}
int sub(int a, int b){
return a-b;
}
預處理階段
預處理用於擴充套件原始碼,插入所有的#include命令指定的檔案,並擴充套件所有用#define宣告指定的巨集。預處理之後得到的仍然是文字檔案,但檔案體積會大很多。gcc的預處理是前處理器cpp來完成的,你可以通過如下命令對test.c進行預處理:
gcc -E -I./inc test.c -o test.i
或者直接呼叫cpp命令
cpp test.c -I./inc -o test.i
上述命令中-E是讓編譯器在預處理之後就退出,不進行後續編譯過程;-I指定標頭檔案目錄,這裡指定的是我們自定義的標頭檔案目錄;-o指定輸出檔名。
編譯(Compilation)階段
gcc -S -I./inc test.c -o test.s
上述命令中-S讓編譯器在編譯之後停止,不進行後續過程。編譯過程完成後,將生成程式的彙編程式碼test.s,這也是文字檔案,內容如下:
// test.c彙編之後的結果test.s
.file "test.c"
.section .rodata
.LC0:
.string "a=%d, b=%d, a+b=%d
"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
movl $2, 20(%esp)
movl $3, 24(%esp)
movl 24(%esp), %eax
movl %eax, 4(%esp)
movl 20(%esp), %eax
movl %eax, (%esp)
call add
movl %eax, 28(%esp)
movl 28(%esp), %eax
movl %eax, 12(%esp)
movl 24(%esp), %eax
movl %eax, 8(%esp)
movl 20(%esp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
彙編(Assemble)階段
彙編過程將上一步的彙編程式碼轉換成機器碼(machine code),這一步產生的檔案叫做目標檔案,是二進位制格式。gcc彙編過程通過as命令完成:
$ as test.s -o test.o
等價於:
gcc -c test.s -o test.o
這一步會為每一個原始檔產生一個目標檔案。因此mymath.c也需要產生一個mymath.o檔案
連結(Linking)階段
連結過程將多個目標文以及所需的庫檔案(.so等)連結成最終的可執行檔案(executable file)。
命令大致如下:
$ ld -o test.out test.o inc/mymath.o ...libraries...
幾種語言的編譯執行本質區別:
PHP:執行時編譯為opcode,然後zend引擎執行opcode
Java:先編譯成位元組碼,然後由JVM虛擬機器執行位元組碼
C:直接編譯成可執行檔案,然後由作業系統執行可以行檔案
參考資料:
http://tina.reeze.cn/book/
http://www.laruence.com/2008/…
http://rednaxelafx.iteye.com/…
http://www.vcgood.com/archive…
http://www.cnblogs.com/Carpen…
http://blog.csdn.net/cutesour…
http://www.nowamagic.net/libr…