【開發語言】PHP、Java、C語言的編譯執行過程

編譯型語言和解釋型語言

從PHP,Java和C語言的編譯執行過程能夠先解釋下編譯型語言和解釋型語言。php

  • 編譯型語言html

程序在執行以前須要一個專門的編譯過程,把程序編譯成爲機器語言的文件,運行時不須要從新翻譯,直接使用編譯的結果就好了。程序執行效率高,依賴編譯器,跨平臺性差些。如C、C++、Delphi等.java

  • 解釋型語言express

程序不須要編譯,程序在運行時才翻譯成機器語言,每執行一次都要翻譯一次。所以效率比較低。好比Basic語言,專門有一個解釋器可以直接執行Basic程序,每一個語句都是執行的時候才翻譯。(在運行程序的時候才翻譯,專門有一個解釋器去進行翻譯,每一個語句都是執行的時候才翻譯。效率比較低,依賴解釋器,跨平臺性好.)ubuntu

PHP語言編譯執行過程

下面都是鳥哥博客的內容:深刻理解PHP原理之opcodeide

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轉換成一個一個的簡單的表達式.net

> 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\n", 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\n"
    .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...

相關文章
相關標籤/搜索