『壹』 DLL動態鏈接庫如何共享數據和如何創建純資源DLL
一、DLL動態鏈接庫如何共享數據
上次有人問起一個關於DLL編寫的問題:他創建了一個DLL動態鏈接庫,裡面寫了一個滑鼠鉤子,用於監視滑鼠左鍵的點擊;另外創建了一個普通MFC應用程序,裡面可以通過點擊按鈕載入這個DLL庫並安裝鉤子。鉤子的效果是:當用戶在屏幕的任何地方點擊滑鼠左鍵,都會導致MFC應用程序裡面的計數器的加一。可是,程序編寫完後,問題出現了:當他在MFC應用程序窗口內單擊滑鼠左鍵,計數器能夠成功自增;可是一旦在窗口之外的地方左擊,則不能自增,即使他把那個MFC應用程序設置為了「頂級窗口」——即無論是否有焦點,始終在桌面最前端。
我開始也很不解,懷疑他是否正確創建了鉤子。鉤子(hook)有兩種,即進程相關鉤子,和全局鉤子。進程相關鉤子只對指定的進程起作用,例如:
SetWindowsHookEx(WH_GETMESSAGE,HookProc, hMod, dwThreadId);
而全局鉤子則對所有進程起作用,例如:
SetWindowsHookEx(WH_GETMESSAGE,HookProc, hMod, NULL);
仔細查看他的代碼後,發現鉤子編寫是正確的,將這種可能排除。
最後終於發現——這主要得益於網友Hc1的提醒,在這里說一聲謝謝!——原來是在DLL工程中,有一個和計數器密切相關的變數i沒有共享,也就是說,對於不同的進程,他所看到的i是不同的。我們知道,動態鏈接庫最大的好處是不同的進程可以共享,節省內存。但是,這種共享是有條件的,也就是說,被允許共享的代碼和變數才能真正被共享,否則默認的是不共享。所以在這里我們需要將i設置為共享:
#pragma data_seg(「.MyData」)
int i=0;
#pragma data_seg()
注意,被共享的變數一定要初始化,否則,編譯器仍然會把它作為非共享變數。
最後,在DEF文件中添加如下語句:
SECTIONS
.MyData READ WRITE SHARED
「.MyData」表示這段共享數據的名字,可以任意,甚至是中文。
編譯結束後,我們可以通過命令行運行mpbin命令查看dll的導出情況,可以發現確實導出了數據段「.MyData」。
二、如何創建純資源DLL
有時候,我們需要創建純資源的DLL文件,用來儲存一些資源,例如我們可以通過創建純資源DLL來解決軟體的國際化問題(DLL裡麵包含多國語言資源,就可以實現安裝時選擇用戶所使用的語言這一個性化功能)。編寫純資源DLL要比編寫一般的DLL簡單的多,例如不需要編寫DEF導出文件等。
首先新建一個Win32 DLL工程。然後用「新建」命令創建一個「Resource Script」文件,在出現的RS編輯窗口中,單擊滑鼠右鍵,就可以添加各種資源,並可以對資源進行編輯。接著,在通過「新建」創建一個Cpp文件。雖然,後面我們會看到,我們可以不對這個空白的Cpp源文件進行任何操作,但是我們仍然需要新建一個Cpp,原因我不太清除,是實驗測試的時候發現的。最後,單擊「Project」,再「Settings」,再「Link」,在「Project Options」編輯框裡面,添加「/noentry」,以避免編譯器自動為我們的資源DLL文件添加默認的_main或者DllMain入口函數。事實上,經過測試,不設置這個「noentry」,工程照樣可以成功編譯,也可以在其他工程中使用正常,但是大小卻要增加很多,對於純資源的DLL文件來說,這些增加的體積毫無必要,所以我們還是需要手動添加這么一個命令。
如何在在普通MFC應用程序工程中使用我們寫好的純資源DLL呢?首先,需要定義資源宏。例如,在純資源的DLL裡面有一張名為:IDB_BITMAP1的點陣圖,那麼一般VC編譯器默認的資源宏定義是從100開始的,所以在純資源DLL裡面有這樣的宏:
#define IDB_BITMAP1 101
這句宏定義是在Resource.h頭文件當中的。
那麼我們也要在MFC應用程序中定義同樣的宏,否則無法載入這個點陣圖。
然後,我們需要載入DLL文件(在這里,我們認為開始時創建的動態鏈接庫為A.dll):
HINSTANCE hDll=LoadLibrary("A.dll");
if(hDll==NULL)
{
MessageBox(「Load Error!」);
return;
}
載入並測試是否成功,否則返回。
然後,我們就可以像在正常MFC應用程序裡面添加資源一樣載入DLL裡面的資源了:
HBITMAP hBit=(HBITMAP)::LoadImage(hDll,
MAKEINTRESOURCE(IDB_BITMAP1),
IMAGE_BITMAP,
0, 0,
LR_DEFAULTCOLOR);
if(hBit==NULL)
{
MessageBox("hBit is NULL");
return;
}
CBitmap bitmap;
bitmap.Attach(hBit);
CClientDC dc(this);
CDC dcCom;
dcCom.CreateCompatibleDC(&dc);
dcCom.SelectObject(&bitmap);
dc.BitBlt(0, 0, 500, 500, &dcCom, 0, 0, SRCCOPY);
ReleaseDC(&dcCom);
當然,最後在使用完DLL之後,不要忘了卸載DLL:
FreeLibrary(hDll);
這樣就完成了。
『貳』 現代C/C++編譯器有多智能能做出什麼厲害的優化
基本的循環外提,計算削弱,數據流分析合並都已經比較成熟了,可以說不考慮專用cpu優化的情況,其優化效率已經可以和一名中高級編譯專家手工優化的效果差不多。
『叄』 java里,棧有一個很重要的特殊性,就是存在棧中的數據可以共享,這是什麼意思
是這樣的
a=3就是a指向3,然後a = 4就是a指向4,這是編譯後的運行方式,和代碼中的引用傳遞是有區別的。
區別在於,a值的改變不會影響到b的值,即使a和b是對象,b=c(c是另一個對象),a的值也不變;但是b.d=c,那麼a.d也會變成c。
你可以這樣想:
值傳遞的變數就是指向值的指針;
而引用傳遞(類)就是指向一組指針的指針。
實際賦值時都是指針等於指針。
『肆』 編譯器的發展史
編譯器
編譯器,是將便於人編寫,閱讀,維護的高級計算機語言翻譯為計算機能識別,運行的低級機器語言的程序。編譯器將源程序(Source program)作為輸入,翻譯產生使用目標語言(Target language)的等價程序。源程序一般為高級語言(High-level language),如Pascal,C++等,而目標語言則是匯編語言或目標機器的目標代碼(Object code),有時也稱作機器代碼(Machine code)。
一個現代編譯器的主要工作流程如下:
源程序(source code)→預處理器(preprocessor)→編譯器(compiler)→匯編程序(assembler)→目標程序(object code)→連接器(鏈接器,Linker)→可執行程序(executables)
目錄 [隱藏]
1 工作原理
2 編譯器種類
3 預處理器(preprocessor)
4 編譯器前端(frontend)
5 編譯器後端(backend)
6 編譯語言與解釋語言對比
7 歷史
8 參見
工作原理
翻譯是從源代碼(通常為高級語言)到能直接被計算機或虛擬機執行的目標代碼(通常為低級語言或機器言)。然而,也存在從低級語言到高級語言的編譯器,這類編譯器中用來從由高級語言生成的低級語言代碼重新生成高級語言代碼的又被叫做反編譯器。也有從一種高級語言生成另一種高級語言的編譯器,或者生成一種需要進一步處理的的中間代碼的編譯器(又叫級聯)。
典型的編譯器輸出是由包含入口點的名字和地址以及外部調用(到不在這個目標文件中的函數調用)的機器代碼所組成的目標文件。一組目標文件,不必是同一編譯器產生,但使用的編譯器必需採用同樣的輸出格式,可以鏈接在一起並生成可以由用戶直接執行的可執行程序。
編譯器種類
編譯器可以生成用來在與編譯器本身所在的計算機和操作系統(平台)相同的環境下運行的目標代碼,這種編譯器又叫做「本地」編譯器。另外,編譯器也可以生成用來在其它平台上運行的目標代碼,這種編譯器又叫做交叉編譯器。交叉編譯器在生成新的硬體平台時非常有用。「源碼到源碼編譯器」是指用一種高級語言作為輸入,輸出也是高級語言的編譯器。例如: 自動並行化編譯器經常採用一種高級語言作為輸入,轉換其中的代碼,並用並行代碼注釋對它進行注釋(如OpenMP)或者用語言構造進行注釋(如FORTRAN的DOALL指令)。
預處理器(preprocessor)
作用是通過代入預定義等程序段將源程序補充完整。
編譯器前端(frontend)
前端主要負責解析(parse)輸入的源程序,由詞法分析器和語法分析器協同工作。詞法分析器負責把源程序中的『單詞』(Token)找出來,語法分析器把這些分散的單詞按預先定義好的語法組裝成有意義的表達式,語句 ,函數等等。 例如「a = b + c;」前端詞法分析器看到的是「a, =, b , +, c;」,語法分析器按定義的語法,先把他們組裝成表達式「b + c」,再組裝成「a = b + c」的語句。 前端還負責語義(semantic checking)的檢查,例如檢測參與運算的變數是否是同一類型的,簡單的錯誤處理。最終的結果常常是一個抽象的語法樹(abstract syntax tree,或 AST),這樣後端可以在此基礎上進一步優化,處理。
編譯器後端(backend)
編譯器後端主要負責分析,優化中間代碼(Intermediate representation)以及生成機器代碼(Code Generation)。
一般說來所有的編譯器分析,優化,變型都可以分成兩大類: 函數內(intraproceral)還是函數之間(interproceral)進行。很明顯,函數間的分析,優化更准確,但需要更長的時間來完成。
編譯器分析(compiler analysis)的對象是前端生成並傳遞過來的中間代碼,現代的優化型編譯器(optimizing compiler)常常用好幾種層次的中間代碼來表示程序,高層的中間代碼(high level IR)接近輸入的源程序的格式,與輸入語言相關(language dependent),包含更多的全局性的信息,和源程序的結構;中層的中間代碼(middle level IR)與輸入語言無關,低層的中間代碼(Low level IR)與機器語言類似。 不同的分析,優化發生在最適合的那一層中間代碼上。
常見的編譯分析有函數調用樹(call tree),控制流程圖(Control flow graph),以及在此基礎上的 變數定義-使用,使用-定義鏈(define-use/use-define or u-d/d-u chain),變數別名分析(alias analysis),指針分析(pointer analysis),數據依賴分析(data dependence analysis)等等。
上述的程序分析結果是編譯器優化(compiler optimization)和程序變形(compiler transformation)的前提條件。常見的優化和變新有:函數內嵌(inlining),無用代碼刪除(Dead code elimination),標准化循環結構(loop normalization),循環體展開(loop unrolling),循環體合並,分裂(loop fusion,loop fission),數組填充(array padding),等等。 優化和變形的目的是減少代碼的長度,提高內存(memory),緩存(cache)的使用率,減少讀寫磁碟,訪問網路數據的頻率。更高級的優化甚至可以把序列化的代碼(serial code)變成並行運算,多線程的代碼(parallelized,multi-threaded code)。
機器代碼的生成是優化變型後的中間代碼轉換成機器指令的過程。現代編譯器主要採用生成匯編代碼(assembly code)的策略,而不直接生成二進制的目標代碼(binary object code)。即使在代碼生成階段,高級編譯器仍然要做很多分析,優化,變形的工作。例如如何分配寄存器(register allocatioin),如何選擇合適的機器指令(instruction selection),如何合並幾句代碼成一句等等。
編譯語言與解釋語言對比
許多人將高級程序語言分為兩類: 編譯型語言 和 解釋型語言 。然而,實際上,這些語言中的大多數既可用編譯型實現也可用解釋型實現,分類實際上反映的是那種語言常見的實現方式。(但是,某些解釋型語言,很難用編譯型實現。比如那些允許 在線代碼更改 的解釋型語言。)
歷史
上世紀50年代,IBM的John Backus帶領一個研究小組對FORTRAN語言及其編譯器進行開發。但由於當時人們對編譯理論了解不多,開發工作變得既復雜又艱苦。與此同時,Noam Chomsky開始了他對自然語言結構的研究。他的發現最終使得編譯器的結構異常簡單,甚至還帶有了一些自動化。Chomsky的研究導致了根據語言文法的難易程度以及識別它們所需要的演算法來對語言分類。正如現在所稱的Chomsky架構(Chomsky Hierarchy),它包括了文法的四個層次:0型文法、1型文法、2型文法和3型文法,且其中的每一個都是其前者的特殊情況。2型文法(或上下文無關文法)被證明是程序設計語言中最有用的,而且今天它已代表著程序設計語言結構的標准方式。分析問題(parsing problem,用於上下文無關文法識別的有效演算法)的研究是在60年代和70年代,它相當完善的解決了這個問題。現在它已是編譯原理中的一個標准部分。
有限狀態自動機(Finite Automaton)和正則表達式(Regular Expression)同上下文無關文法緊密相關,它們與Chomsky的3型文法相對應。對它們的研究與Chomsky的研究幾乎同時開始,並且引出了表示程序設計語言的單詞的符號方式。
人們接著又深化了生成有效目標代碼的方法,這就是最初的編譯器,它們被一直使用至今。人們通常將其稱為優化技術(Optimization Technique),但因其從未真正地得到過被優化了的目標代碼而僅僅改進了它的有效性,因此實際上應稱作代碼改進技術(Code Improvement Technique)。
當分析問題變得好懂起來時,人們就在開發程序上花費了很大的功夫來研究這一部分的編譯器自動構造。這些程序最初被稱為編譯器的編譯器(Compiler-compiler),但更確切地應稱為分析程序生成器(Parser Generator),這是因為它們僅僅能夠自動處理編譯的一部分。這些程序中最著名的是Yacc(Yet Another Compiler-compiler),它是由Steve Johnson在1975年為Unix系統編寫的。類似的,有限狀態自動機的研究也發展了一種稱為掃描程序生成器(Scanner Generator)的工具,Lex(與Yacc同時,由Mike Lesk為Unix系統開發)是這其中的佼佼者。
在70年代後期和80年代早期,大量的項目都貫注於編譯器其它部分的生成自動化,這其中就包括了代碼生成。這些嘗試並未取得多少成功,這大概是因為操作太復雜而人們又對其不甚了解。
編譯器設計最近的發展包括:首先,編譯器包括了更加復雜演算法的應用程序它用於推斷或簡化程序中的信息;這又與更為復雜的程序設計語言的發展結合在一起。其中典型的有用於函數語言編譯的Hindley-Milner類型檢查的統一演算法。其次,編譯器已越來越成為基於窗口的交互開發環境(Interactive Development Environment,IDE)的一部分,它包括了編輯器、連接程序、調試程序以及項目管理程序。這樣的IDE標准並沒有多少,但是對標準的窗口環境進行開發已成為方向。另一方面,盡管近年來在編譯原理領域進行了大量的研究,但是基本的編譯器設計原理在近20年中都沒有多大的改變,它現在正迅速地成為計算機科學課程中的中心環節。
在九十年代,作為GNU項目或其它開放源代碼項目的一部分,許多免費編譯器和編譯器開發工具被開發出來。這些工具可用來編譯所有的計算機程序語言。它們中的一些項目被認為是高質量的,而且對現代編譯理論感性趣的人可以很容易的得到它們的免費源代碼。
大約在1999年,SGI公布了他們的一個工業化的並行化優化編譯器Pro64的源代碼,後被全世界多個編譯器研究小組用來做研究平台,並命名為Open64。Open64的設計結構好,分析優化全面,是編譯器高級研究的理想平台。
編譯器是一種特殊的程序,它可以把以特定編程語言寫成的程序變為機器可以運行的機器碼。我們把一個程序寫好,這時我們利用的環境是文本編輯器。這時我程序把程序稱為源程序。在此以後程序員可以運行相應的編譯器,通過指定需要編譯的文件的名稱就可以把相應的源文件(通過一個復雜的過程)轉化為機器碼了。
編譯器工作方法
首先編譯器進行語法分析,也就是要把那些字元串分離出來。然後進行語義分析,就是把各個由語法分析分析出的語法單元的意義搞清楚。最後生成的是目標文件,我們也稱為obj文件。再經過鏈接器的鏈接就可以生成最後的可執行代碼了。有些時候我們需要把多個文件產生的目標文件進行鏈接,產生最後的代碼。我們把一過程稱為交叉鏈接。
『伍』 求解,怎麼主程序和DLL子模塊共享數據
後綴為dll的資料庫文件為動態鏈接庫,要載入到軟體上才能用。
附:DLL文件(Dynamic Linkable Library 即動態鏈接庫文件),是一種不能單獨運行的文件,它允許程序共享執行特殊任務所必需的代碼和其他資源
比較大的應用程序都由很多模塊組成,這些模塊分別完成相對獨立的功能,它們彼此協作來完成整個軟體系統的工作。可能存在一些模塊的功能較為通用,在構造其它軟體系統時仍會被使用。在構造軟體系統時,如果將所有模塊的源代碼都靜態編譯到整個應用程序 EXE 文件中,會產生一些問題:一個缺點是增加了應用程序的大小,它會佔用更多的磁碟空間,程序運行時也會消耗較大的內存空間,造成系統資源的浪費;另一個缺點是,在編寫大的 EXE 程序時,在每次修改重建時都必須調整編譯所有源代碼,增加了編譯過程的復雜性,也不利於階段性的單元測試。
Windows 系統平台上提供了一種完全不同的較有效的編程和運行環境,你可以將獨立的程序模塊創建為較小的 DLL 文件,並可對它們單獨編譯和測試。在運行時,只有當 EXE 程序確實要調用這些 DLL 模塊的情況下,系統才會將它們裝載到內存空間中。這種方式不僅減少了 EXE 文件的大小和對內存空間的需求,而且使這些 DLL 模塊可以同時被多個應用程序使用。Windows 自己就將一些主要的系統功能以 DLL 模塊的形式實現。
一般來說,DLL 是一種磁碟文件,以.dll、.DRV、.FON、.SYS 和許多以 .EXE 為擴展名的系統文件都可以是 DLL。它由全局數據、服務函數和資源組成,在運行時被系統載入到調用進程的虛擬空間中,成為調用進程的一部分。如果與其它 DLL 之間沒有沖突,該文件通常映射到進程虛擬空間的同一地址上。DLL 模塊中包含各種導出函數,用於向外界提供服務。DLL 可以有自己的數據段,但沒有自己的堆棧,使用與調用它的應用程序相同的堆棧模式;一個 DLL 在內存中只有一個實例;DLL 實現了代碼封裝性;DLL 的編制與具體的編程語言及編譯器無關。
在 Win32 環境中,每個進程都復制了自己的讀/寫全局變數。如果想要與其它進程共享內存,必須使用內存映射文件或者聲明一個共享數據段。DLL 模塊需要的堆棧內存都是從運行進程的堆棧中分配出來的。Windows 在載入 DLL 模塊時將進程函數調用與 DLL 文件的導出函數相匹配。Windows 操作系統對 DLL 的操作僅僅是把 DLL 映射到需要它的進程的虛擬地址空間里去。DLL 函數中的代碼所創建的任何對象(包括變數)都歸調用它的線程或進程所有。
調用方式:
1、靜態調用方式:由編譯系統完成對 DLL 的載入和應用程序結束時 DLL 卸載的編碼(如還有其它程序使用該 DLL,則 Windows 對 DLL 的應用記錄減1,直到所有相關程序都結束對該 DLL 的使用時才釋放它,簡單實用,但不夠靈活,只能滿足一般要求。
隱式的調用:需要把產生動態連接庫時產生的 .LIB 文件加入到應用程序的工程中,想使用 DLL 中的函數時,只須說明一下。隱式調用不需要調用 LoadLibrary() 和 FreeLibrary()。程序員在建立一個 DLL 文件時,鏈接程序會自動生成一個與之對應的 LIB 導入文件。該文件包含了每一個 DLL 導出函數的符號名和可選的標識號,但是並不含有實際的代碼。LIB 文件作為 DLL 的替代文件被編譯到應用程序項目中。
當程序員通過靜態鏈接方式編譯生成應用程序時,應用程序中的調用函數與 LIB 文件中導出符號相匹配,這些符號或標識號進入到生成的 EXE 文件中。LIB 文件中也包含了對應的 DL L文件名(但不是完全的路徑名),鏈接程序將其存儲在 EXE 文件內部。
當應用程序運行過程中需要載入 DLL 文件時,Windows 根據這些信息發現並載入 DLL,然後通過符號名或標識號實現對 DLL 函數的動態鏈接。所有被應用程序調用的 DLL 文件都會在應用程序 EXE文件載入時被載入在到內存中。可執行程序鏈接到一個包含 DLL 輸出函數信息的輸入庫文件(.LIB文件)。操作系統在載入使用可執行程序時載入 DLL。可執行程序直接通過函數名調用 DLL 的輸出函數,調用方法和程序內部其 它的函數是一樣的。
2、動態調用方式:是由編程者用 API 函數載入和卸載 DLL 來達到調用 DLL 的目的,使用上較復雜,但能更加有效地使用內存,是編制大型應用程序時的重要方式。
顯式的調用:
是指在應用程序中用 LoadLibrary 或 MFC 提供的 AfxLoadLibrary 顯式的將自己所做的動態連接庫調進來,動態連接庫的文件名即是上面兩個函數的參數,再用 GetProcAddress() 獲取想要引入的函數。自此,你就可以象使用如同本應用程序自定義的函數一樣來調用此引入函數了。在應用程序退出之前,應該用 FreeLibrary 或 MFC 提供的 AfxFreeLibrary 釋放動態連接庫。直接調用 Win32 的 LoadLibary 函數,並指定 DLL 的路徑作為參數。LoadLibary 返回 HINSTANCE 參數,應用程序在調用 GetProcAddress 函數時使用這一參數。GetProcAddress 函數將符號名或標識號轉換為 DLL 內部的地址。程序員可以決定 DLL 文件何時載入或不載入,顯式鏈接在運行時決定載入哪個 DLL 文件。使用 DLL 的程序在使用之前必須載入(LoadLibrary)載入DLL從而得到一個DLL模塊的句柄,然後調用 GetProcAddress 函數得到輸出函數的指針,在退出之前必須卸載DLL(FreeLibrary)。
正因為DLL 有佔用內存小,好編輯等的特點有很多電腦病毒都是DLL格式文件。但不能單獨運行。
動態鏈接庫通常都不能直接運行,也不能接收消息。它們是一些獨立的文件,其中包含能被可執行程序或其它DLL調用來完成某項工作的函數。只有在其它模塊調用動態鏈接庫中的函數時,它才發揮作用。
『陸』 了解什麼叫做jit compiling,與傳統的編譯技術有何不同
Java 應用程序的性能經常成為開發社區中的討論熱點。因為該語言的設計初衷是使用解釋的方式支持應用程序的可移植性目標,早期
Java 運行時所提供的性能級別遠低於 C 和
C++
之類的編譯語言。盡管這些語言可以提供更高的性能,但是生成的代碼只能在有限的幾種系統上執行。在過去的十年中,Java
運行時供應商開發了一些復雜的動態編譯器,通常稱作即時(Just-in-time,JIT)編譯器。程序運行時,JIT
編譯器選擇將最頻繁執行的方法編譯成本地代碼。運行時才進行本地代碼編譯而不是在程序運行前進行編譯(用 C 或
C++ 編寫的程序正好屬於後一情形),保證了可移植性的需求。有些 JIT 編譯器甚至不使用解釋程序就能編譯所有的代碼,但是這些編譯器仍然通過在程序執行時進行一些操作來保持 Java 應用程序的可移植性。
由於動態編譯技術的多項改進,在很多應用程序中,現代的 JIT 編譯器可以產生與 C 或 C++
靜態編譯相當的應用程序性能。但是,仍然有很多軟體開發人員認為 —— 基於經驗或者傳聞 ——
動態編譯可能嚴重干擾程序操作,因為編譯器必須與應用程序共享 CPU。一些開發人員強烈呼籲對 Java
代碼進行靜態編譯,並且堅信那樣可以解決性能問題。對於某些應用程序和執行環境而言,這種觀點是正確的,靜態編譯可以極大地提高 Java
性能,或者說它是惟一的實用選擇。但是,靜態地編譯 Java 應用程序在獲得高性能的同時也帶來了很多復雜性。一般的
Java 開發人員可能並沒有充分地感受到 JIT 動態編譯器的優點。
本文考察了 Java 語言靜態編譯和動態編譯所涉及的一些問題,重點介紹了實時 (RT) 系統。簡要描述了 Java
語言解釋程序的操作原理並說明了現代 JIT 編譯器執行本地代碼編譯的優缺點。介紹了 IBM 在 WebSphere Real Time 中發布的
AOT 編譯技術和它的一些優缺點。然後比較了這兩種編譯策略並指出了幾種比較適合使用 AOT
編譯的應用程序領域和執行環境。要點在於這兩種編譯技術並不互斥:即使在使用這兩種技術最為有效的各種應用程序中,它們也分別存在一些影響應用程序的優缺
點。
執行 Java 程序
Java 程序最初是通過 Java SDK 的 javac程序編譯成本地的與平台無關的格式(類文件)。可將此格式看作 Java
平台,因為它定義了執行 Java 程序所需的所有信息。Java 程序執行引擎,也稱作 Java 運行時環境(JRE),包含了為特定的本地平台實現
Java 平台的虛擬機。例如,基於 Linux 的 Intel x86 平台、Sun Solaris 平台和 AIX 操作系統上運行的 IBM
System p 平台,每個平台都擁有一個 JRE。這些 JRE 實現實現了所有的本地支持,從而可以正確執行為
Java 平台編寫的程序。
事實上,操作數堆棧的大小有實際限制,但是編程人員極少編寫超出該限制的方法。JVM 提供了安全性檢查,對那些創建出此類方法的編程人員進行通知。
Java 平台程序表示的一個重要部分是位元組碼序列,它描述了 Java
類中每個方法所執行的操作。位元組碼使用一個理論上無限大的操作數堆棧來描述計算。這個基於堆棧的程序表示提供了平台無關性,因為它不依賴任何特定本地平台
的 CPU 中可用寄存器的數目。可在操作數堆棧上執行的操作的定義都獨立於所有本地處理器的指令集。Java
虛擬機(JVM)規范定義了這些位元組碼的執行(參見 參考資料)。執行 Java 程序時,用於任何特定本地平台的任何 JRE 都必須遵守 JVM
規范中列出的規則。
因為基於堆棧的本地平台很少(Intel X87 浮點數協處理器是一個明顯的例外),所以大多數本地平台不能直接執行 Java 位元組碼。為了解決這個問題,早期的 JRE 通過解釋位元組碼來執行 Java 程序。即 JVM 在一個循環中重復操作:
◆獲取待執行的下一個位元組碼;
◆解碼;
◆從操作數堆棧獲取所需的操作數;
◆按照 JVM 規范執行操作;
◆將結果寫回堆棧。
這種方法的優點是其簡單性:JRE 開發人員只需編寫代碼來處理每種位元組碼即可。並且因為用於描述操作的位元組碼少於 255 個,所以實現的成本比較低。當然,缺點是性能:這是一個早期造成很多人對 Java 平台不滿的問題,盡管擁有很多其他優點。
解決與 C 或 C++ 之類的語言之間的性能差距意味著,使用不會犧牲可移植性的方式開發用於 Java 平台的本地代碼編譯。
編譯 Java 代碼
盡管傳聞中 Java 編程的 「一次編寫,隨處運行」
的口號可能並非在所有情況下都嚴格成立,但是對於大量的應用程序來說情況確實如此。另一方面,本地編譯本質上是特定於平台的。那麼 Java
平台如何在不犧牲平台無關性的情況下實現本地編譯的性能?答案就是使用 JIT 編譯器進行動態編譯,這種方法已經使用了十年(參見圖 1):
圖 1. JIT 編譯器
使用 JIT 編譯器時,Java
程序按每次編譯一個方法的形式進行編譯,因為它們在本地處理器指令中執行以獲得更高的性能。此過程將生成方法的一個內部表示,該表示與位元組碼不同但是其級
別要高於目標處理器的本地指令。(IBM JIT
編譯器使用一個表達式樹序列表示方法的操作。)編譯器執行一系列優化以提高質量和效率,最後執行一個代碼生成步驟將優化後的內部表示轉換成目標處理器的本
地指令。生成的代碼依賴運行時環境來執行一些活動,比如確保類型轉換的合法性或者對不能在代碼中直接執行的某些類型的對象進行分配。JIT
編譯器操作的編譯線程與應用程序線程是分開的,因此應用程序不需要等待編譯的執行。
圖 1 中還描述了用於觀察執行程序行為的分析框架,通過周期性地對線程取樣找出頻繁執行的方法。該框架還為專門進行分析的方法提供了工具,用來存儲程序的此次執行中可能不會改變的動態值。
因為這個 JIT 編譯過程在程序執行時發生,所以能夠保持平台無關性:發布的仍然是中立的 Java 平台代碼。C 和 C++ 之類的語言缺乏這種優點,因為它們在程序執行前進行本地編譯;發布給(本地平台)執行環境的是本地代碼。
挑戰
盡管通過 JIT 編譯保持了平台無關性,但是付出了一定代價。因為在程序執行時進行編譯,所以編譯代碼的時間將計入程序的執行時間。任何編寫過大型 C 或 C++ 程序的人都知道,編譯過程往往較慢。
為了克服這個缺點,現代的 JIT
編譯器使用了下面兩種方法的任意一種(某些情況下同時使用了這兩種方法)。第一種方法是:編譯所有的代碼,但是不執行任何耗時多的分析和轉換,因此可以快
速生成代碼。由於生成代碼的速度很快,因此盡管可以明顯觀察到編譯帶來的開銷,但是這很容易就被反復執行本地代碼所帶來的性能改善所掩蓋。第二種方法是:
將編譯資源只分配給少量的頻繁執行的方法(通常稱作熱方法)。低編譯開銷更容易被反復執行熱代碼帶來的性能優勢掩蓋。很多應用程序只執行少量的熱方法,因
此這種方法有效地實現了編譯性能成本的最小化。
動態編譯器的一個主要的復雜性在於權衡了解編譯代碼的預期獲益使方法的執行對整個程序的性能起多大作用。一個極端的例子是,程序執行後,您非常清楚哪些方
法對於這個特定的執行的性能貢獻最大,但是編譯這些方法毫無用處,因為程序已經完成。而在另一個極端,程序執行前無法得知哪些方法重要,但是每種方法的潛
在受益都最大化了。大多數動態編譯器的操作介於這兩個極端之間,方法是權衡了解方法預期獲益的重要程度。
Java 語言需要動態載入類這一事實對 Java
編譯器的設計有著重要的影響。如果待編譯代碼引用的其他類還沒有載入怎麼辦?比如一個方法需要讀取某個尚未載入的類的靜態欄位值。Java
語言要求第一次執行類引用時載入這個類並將其解析到當前的 JVM
中。直到第一次執行時才解析引用,這意味著沒有地址可供從中載入該靜態欄位。編譯器如何處理這種可能性?編譯器生成一些代碼,用於在沒有載入類時載入並解
析類。類一旦被解析,就會以一種線程安全的方式修改原始代碼位置以便直接訪問靜態欄位的地址,因為此時已獲知該地址。
IBM JIT
編譯器中進行了大量的努力以便使用安全而有效率的代碼補丁技術,因此在解析類之後,執行的本地代碼只載入欄位的值,就像編譯時已經解析了欄位一樣。另外一
種方法是生成一些代碼,用於在查明欄位的位置以前一直檢查是否已經解析欄位,然後載入該值。對於那些由未解析變成已解析並被頻繁訪問的欄位來說,這種簡單
的過程可能帶來嚴重的性能問題。
動態編譯的優點
動態地編譯 Java 程序有一些重要的優點,甚至能夠比靜態編譯語言更好地生成代碼,現代的 JIT 編譯器常常向生成的代碼中插入掛鉤以收集有關程序行為的信息,以便如果要選擇方法進行重編譯,就可以更好地優化動態行為。
關於此方法的一個很好的例子是收集一個特定 array操作的長度。如果發現每次執行操作時該長度基本不變,則可以為最頻繁使用的
array長度生成專門的代碼,或者可以調用調整為該長度的代碼序列。由於內存系統和指令集設計的特性,用於復制內存的最佳通用常式的執行速度通
常比用於復制特定長度的代碼慢。例如,復制 8
個位元組的對齊的數據可能需要一到兩條指令直接復制,相比之下,使用可以處理任意位元組數和任意對齊方式的一般復制循環可能需要 10 條指令來復制同樣的 8
個位元組。但是,即使此類專門的代碼是為某個特定的長度生成的,生成的代碼也必須正確地執行其他長度的復制。生成代碼只是為了使常見長度的操作執行得更快,
因此平均下來,性能得到了改進。此類優化對大多數靜態編譯語言通常不實用,因為所有可能的執行中長度恆定的操作比一個特定程序執行中長度恆定的操作要少得
多。
此類優化的另一個重要的例子是基於類層次結構的優化。例如,一個虛方法調用需要查看接收方對象的類調用,以便找出哪個實際目標實現了接收方對象的虛方法。
研究表明:大多數虛調用只有一個目標對應於所有的接收方對象,而 JIT
編譯器可以為直接調用生成比虛調用更有效率的代碼。通過分析代碼編譯後類層次結構的狀態,JIT
編譯器可以為虛調用找到一個目標方法,並且生成直接調用目標方法的代碼而不是執行較慢的虛調用。當然,如果類層次結構發生變化,並且出現另外的目標方法,
則 JIT
編譯器可以更正最初生成的代碼以便執行虛調用。在實踐中,很少需要作出這些更正。另外,由於可能需要作出此類更正,因此靜態地執行這種優化非常麻煩。
因為動態編譯器通常只是集中編譯少量的熱方法,所以可以執行更主動的分析來生成更好的代碼,使編譯的回報更高。事實上,大部分現代的
JIT
編譯器也支持重編譯被認為是熱方法的方法。可以使用靜態編譯器(不太強調編譯時間)中常見的非常主動的優化來分析和轉換這些頻繁執行的方法,以便生成更好
的代碼並獲得更高的性能。
這些改進及其他一些類似的改進所產生的綜合效果是:對於大量的 Java 應用程序來說,動態編譯已經彌補了與 C 和 C++ 之類語言的靜態本地編譯性能之間的差距,在某些情況下,甚至超過了後者的性能。
缺點
但是,動態編譯確實具有一些缺點,這些缺點使它在某些情況下算不上一個理想的解決方案。例如,因為識別頻繁執行的方法以及編譯這些方法需要時間,所以應用
程序通常要經歷一個准備過程,在這個過程中性能無法達到其最高值。在這個准備過程中出現性能問題有幾個原因。首先,大量的初始編譯可能直接影響應用程序的
啟動時間。不僅這些編譯延遲了應用程序達到穩定狀態的時間(想像 Web
伺服器經
歷一個初始階段後才能夠執行實際有用的工作),而且在准備階段中頻繁執行的方法可能對應用程序的穩定狀態的性能所起的作用也不大。如果 JIT
編譯會延遲啟動又不能顯著改善應用程序的長期性能,則執行這種編譯就非常浪費。雖然所有的現代 JVM
都執行調優來減輕啟動延遲,但是並非在所有情況下都能夠完全解決這個問題。
其次,有些應用程序完全不能忍受動態編譯帶來的延遲。如 GUI 介面之類互動式應用程序就是這樣的例子。在這種情況下,編譯活動可能對用戶使用造成不利影響,同時又不能顯著地改善應用程序的性能。
最後,用於實時環境並具有嚴格的任務時限的應用程序可能無法忍受編譯的不確定性性能影響或動態編譯器本身的內存開銷。
因此,雖然 JIT 編譯技術已經能夠提供與靜態語言性能相當(甚至更好)的性能水平,但是動態編譯並不適合於某些應用程序。在這些情況下,Java 代碼的提前(Ahead-of-time,AOT)編譯可能是合適的解決方案。
AOT Java 編譯
大致說來,Java 語言本地編譯應該是為傳統語言(如 C++ 或
Fortran)而開發的編譯技術的一個簡單應用。不幸的是,Java 語言本身的動態特性帶來了額外的復雜性,影響了 Java
程序靜態編譯代碼的質量。但是基本思想仍然是相同的:在程序執行前生成 Java 方法的本地代碼,以便在程序運行時直接使用本地代碼。目的在於避免
JIT 編譯器的運行時性能消耗或內存消耗,或者避免解釋程序的早期性能開銷。
挑戰
動態類載入是動態 JIT 編譯器面臨的一個挑戰,也是 AOT
編譯的一個更重要的問題。只有在執行代碼引用類的時候才載入該類。因為是在程序執行前進行 AOT
編譯的,所以編譯器無法預測載入了哪些類。就是說編譯器無法獲知任何靜態欄位的地址、任何對象的任何實例欄位的偏移量或任何調用的實際目標,甚至對直接調
用(非虛調用)也是如此。在執行代碼時,如果證明對任何這類信息的預測是錯誤的,這意味著代碼是錯誤的並且還犧牲了 Java 的一致性。
因為代碼可以在任何環境中執行,所以類文件可能與代碼編譯時不同。例如,一個 JVM
實例可能從磁碟的某個特定位置載入類,而後面一個實例可能從不同的位置甚至網路載入該類。設想一個正在進行 bug
修復的開發環境:類文件的內容可能隨不同的應用程序的執行而變化。此外,Java 代碼可能在程序執行前根本不存在:比如 Java
反射服務通常在運行時生成新類來支持程序的行為。
缺少關於靜態、欄位、類和方法的信息意味著嚴重限制了 Java 編譯器中優化框架的大部分功能。內聯可能是靜態或動態編譯器應用的最重要的優化,但是由於編譯器無法獲知調用的目標方法,因此無法再使用這種優化。
內聯
內聯是一種用於在運行時生成代碼避免程序開始和結束時開銷的技術,方法是將函數的調用代碼插入到調用方的函數中。但是內聯最大的益處可能是優化方可見的代碼的范圍擴大了,從而能夠生成更高質量的代碼。下面是一個內聯前的代碼示例:
int foo() { int x=2, y=3; return bar(x,y); }final int bar(int a, int b) { return a+b; }
如果編譯器可以證明這個 bar就是 foo()中調用的那個方法,則 bar中的代碼可以取代 foo()中對
bar()的調用。這時,bar()方法是 final類型,因此肯定是 foo()中調用的那個方法。甚至在一些虛調用例子中,動態 JIT
編譯器通常能夠推測性地內聯目標方法的代碼,並且在絕大多數情況下能夠正確使用。編譯器將生成以下代碼:
int foo() { int x=2, y=3; return x+y; }
在這個例子中,簡化前名為值傳播的優化可以生成直接返回
5的代碼。如果不使用內聯,則不能執行這種優化,產生的性能就會低很多。如果沒有解析
bar()方法(例如靜態編譯),則不能執行這種優化,而代碼必須執行虛調用。運行時,實際調用的可能是另外一個執行兩個數字相乘而不是相加的
bar方法。所以不能在 Java 程序的靜態編譯期間直接使用內聯。
AOT
代碼因此必須在沒有解析每個靜態、欄位、類和方法引用的情況下生成。執行時,每個這些引用必須利用當前運行時環境的正確值進行更新。這個過程可能直接影響
第一次執行的性能,因為在第一次執行時將解析所有引用。當然,後續執行將從修補代碼中獲益,從而可以更直接地引用實例、靜態欄位或方法目標。
另外,為 Java 方法生成的本地代碼通常需要使用僅在單個 JVM 實例中使用的值。例如,代碼必須調用 JVM
運行時中的某些運行時常式來執行特定操作,如查找未解析的方法或分配內存。這些運行時常式的地址可能在每次將 JVM 載入到內存時變化。因此 AOT
編譯代碼需要綁定到 JVM 的當前執行環境中,然後才能執行。其他的例子有字元串的地址和常量池入口的內部位置。
在 WebSphere Real Time 中,AOT 本地代碼編譯通過 jxeinajar工具(參見圖 2)來執行。該工具對 JAR 文件中所有類的所有方法應用本地代碼編譯,也可以選擇性地對需要的方法應用本地代碼編譯。結果被存儲到名為 Java eXEcutable (JXE) 的內部格式中,但是也可輕松地存儲到任意的持久性容器中。
您可能認為對所有的代碼進行靜態編譯是最好的方法,因為可以在運行時執行最大數量的本地代碼。但是此處可以作出一些權衡。編譯的方法越多,代碼佔用的內存
就越多。編譯後的本地代碼大概比位元組碼大 10 倍:本地代碼本身的密度比位元組碼小,而且必須包含代碼的附加元數據,以便將代碼綁定到 JVM
中,並且在出現異常或請求堆棧跟蹤時正確執行代碼。構成普通 Java 應用程序的 JAR
文件通常包含許多很少執行的方法。編譯這些方法會消耗內存卻沒有什麼預期收益。相關的內存消耗包括以下過程:將代碼存儲到磁碟上、從磁碟取出代碼並裝入
JVM,以及將代碼綁定到 JVM。除非多次執行代碼,否則這些代價不能由本地代碼相對解釋的性能優勢來彌補。
圖 2. jxeinajar
跟大小問題相違背的一個事實是:在編譯過的方法和解釋過的方法之間進行的調用(即編譯過的方法調用解釋過的方法,或者相反)可能比這兩類方法各自內部之間
進行的調用所需的開銷大。動態編譯器通過最終編譯所有由 JIT
編譯代碼頻繁調用的那些解釋過的方法來減少這項開銷,但是如果不使用動態編譯器,則這項開銷就不可避免。因此如果是選擇性地編譯方法,則必須謹慎操作以使
從已編譯方法到未編譯方法的轉換最小化。為了在所有可能的執行中都避免這個問題而選擇正確的方法會非常困難。
優點
雖然 AOT 編譯代碼具有上述的缺點和挑戰,但是提前編譯 Java 程序可以提高性能,尤其是在不能將動態編譯器作為有效解決方案的環境中。
可以通過謹慎地使用 AOT 編譯代碼加快應用程序啟動,因為雖然這種代碼通常比 JIT
編譯代碼慢,但是卻比解釋代碼快很多倍。此外,因為載入和綁定 AOT
編譯代碼的時間通常比檢測和動態編譯一個重要方法的時間少,所以能夠在程序執行的早期達到那樣的性能。類似地,互動式應用程序可以很快地從本地代碼中獲
益,無需使用引起較差響應能力的動態編譯。
RT 應用程序也能從 AOT 編譯代碼中獲得重要的收益:更具確定性的性能超過了解釋的性能。WebSphere Real Time
使用的動態 JIT 編譯器針對在 RT 系統中的使用進行了專門的調整。使編譯線程以低於 RT
任務的優先順序操作,並且作出了調整以避免生成帶有嚴重的不確定性性能影響的代碼。但是,在一些 RT 環境中,出現 JIT
編譯器是不可接受的。此類環境通常需要最嚴格的時限管理控制。在這些例子中,AOT
編譯代碼可以提供比解釋過的代碼更好的原始性能,又不會影響現有的確定性。消除 JIT
編譯線程甚至消除了啟動更高優先順序 RT 任務時發生的線程搶占所帶來的性能影響。
優缺點統計
動態(JIT)編譯器支持平台中立性,並通過利用應用程序執行的動態行為和關於載入的類及其層次結構的信息來生成高質量的代碼。但是
JIT
編譯器具有一個有限的編譯時預算,而且會影響程序的運行時性能。另一方面,靜態(AOT)編譯器則犧牲了平台無關性和代碼質量,因為它們不能利用程序的動
態行為,也不具有關於載入的類或類層次結構的信息。AOT 編譯擁有有效無限制的編譯時預算,因為 AOT
編譯時間不會影響運行時性能,但是在實踐中開發人員不會長期等待靜態編譯步驟的完成。
表 1 總結了本文討論的 Java 語言動態和靜態編譯器的一些特性:
表 1. 比較編譯技術
兩種技術都需要謹慎選擇編譯的方法以實現最高的性能。對動態編譯器而言,編譯器自身作出決策,而對於靜態編譯器,由開發人員作出選擇。讓
JIT 編譯器選擇編譯的方法是不是優點很難說,取決於編譯器在給定情形中推斷能力的好壞。在大多數情況下,我們認為這是一種優點。
因為它們可以最好地優化運行中的程序,所以 JIT 編譯器在提供穩定狀態性能方面更勝一籌,而這一點在大量的生產 Java
系統中最為重要。靜態編譯可以產生最佳的互動式性能,因為沒有運行時編譯行為來影響用戶預期的響應時間。通過調整動態編譯器可以在某種程度上解決啟動和確
定性性能問題,但是靜態編譯在需要時可提供最快的啟動速度和最高級別的確定性。表 2 在四種不同的執行環境中對這兩種編譯技術進行了比較:
表 2. 使用這些技術的最佳環境
圖 3 展示了啟動性能和穩定狀態性能的總體趨勢:
圖 3. AOT 和 JIT 的性能對比
使用 JIT 編譯器的初始階段性能很低,因為要首先解釋方法。隨著編譯方法的增多及 JIT
執行編譯所需時間的縮短,性能曲線逐漸升高最後達到性能峰值。另一方面,AOT 編譯代碼啟動時的性能比解釋的性能高很多,但是無法達到 JIT
編譯器所能達到的最高性能。將靜態代碼綁定到 JVM 實例中會產生一些開銷,因此開始時的性能比穩定狀態的性能值低,但是能夠比使用 JIT
編譯器更快地達到穩定狀態的性能水平。
沒有一種本地代碼編譯技術能夠適合所有的 Java
執行環境。某種技術所擅長的通常正是其他技術的弱項。出於這個原因,需要同時使用這兩種編譯技術以滿足 Java
應用程序開發人員的要求。事實上,可以結合使用靜態和動態編譯以便提供最大可能的性能提升 —— 但是必須具備平台無關性,它是 Java
語言的主要賣點,因此不成問題。
結束語
本文探討了 Java 語言本地代碼編譯的問題,主要介紹了 JIT 編譯器形式的動態編譯和靜態 AOT 編譯,比較了二者的優缺點。
雖然動態編譯器在過去的十年裡實現了極大的成熟,使大量的各種 Java 應用程序可以趕上或超過靜態編譯語言(如 C++ 或
Fortran)所能夠達到的性能。但是動態編譯在某些類型的應用程序和執行環境中仍然不太合適。雖然 AOT
編譯號稱動態編譯缺點的萬能解決方案,但是由於 Java 語言本身的動態特性,它也面臨著提供本地編譯全部潛能的挑戰。
這兩種技術都不能解決 Java 執行環境中本地代碼編譯的所有需求,但是反過來又可以在最有效的地方作為工具使用。這兩種技術可以相互補充。能夠恰當地使用這兩種編譯模型的運行時系統可以使很大范圍內的應用程序開發環境中的開發人員和用戶受益。
『柒』 C++ 幾個.cpp文件之間如何實現數據共享
全局變數不是這么用的,main.cpp中定義,然後在graph.h中聲明為外部變數就行了
eg.
main.cpp
int pysTotalNumber;
在graph.h中
extern pysTotalNumber;
『捌』 編譯器的功能是什麼
1、編譯器就是將「一種語言(通常為高級語言)」翻譯為「另一種語言(通常為低級語言)」的程序。一個現代編譯器的主要工作流程:源代碼 (source code) → 預處理器 (preprocessor) → 編譯器 (compiler) → 目標代碼 (object code) → 鏈接器(Linker) → 可執行程序 (executables)。
2、工作方法:
1)、首先編譯器進行語法分析,也就是要把那些字元串分離出來。
2)、然後進行語義分析,就是把各個由語法分析分析出的語法單元的意義搞清楚。
3)、最後生成的是目標文件,也稱為obj文件。
4)、再經過鏈接器的鏈接就可以生成最後的EXE文件了。
5)、有些時候需要把多個文件產生的目標文件進行鏈接,產生最後的代碼。這一過程稱為交叉鏈接。
『玖』 編譯器有什麼用
簡單講,編譯器就是將「一種語言(通常為高級語言)」翻譯為「另一種語言(通常為低級語言)」的程序。一個現代編譯器的主要工作流程:源代碼 (source code) → 預處理器 (preprocessor) → 編譯器 (compiler) → 目標代碼 (object code) → 鏈接器(Linker) → 可執行程序 (executables)
高級計算機語言便於人編寫,閱讀交流,維護。機器語言是計算機能直接解讀、運行的。編譯器將匯編或高級計算機語言源程序(Source program)作為輸入,翻譯成目標語言(Target language)機器代碼的等價程序。源代碼一般為高級語言 (High-level language), 如Pascal、C、C++、Java、漢語編程等或匯編語言,而目標則是機器語言的目標代碼(Object code),有時也稱作機器代碼(Machine code)。
對於C#、VB等高級語言而言,此時編譯器完成的功能是把源碼(SourceCode)編譯成通用中間語言(MSIL/CIL)的位元組碼(ByteCode)。最後運行的時候通過通用語言運行庫的轉換,編程最終可以被CPU直接計算的機器碼(NativeCode)。
『拾』 C語言編譯器是用來做什麼的
1.
C語言是一種結構化語言。它層次清晰,便於按模塊化方式組織程序,易於調試和維護。
2.
C語言的表現能力和處理能力極強。它不僅具有豐富的運算符和數據類型,便於實現各類復雜的數據結構。它還可以直接訪問內存的物理地址,進行位(bit)一級的操作。由於C語言實現了對硬體的編程操作,因此C語言集高級語言和低級語言的功能於一體。既可用於系統軟體的開發,也適合於應用軟體的開發。此外,C語言還具有效率高,可移植性強等特點。因此廣泛地移植到了各類各型計算機上,從而形成了多種版本的C語言。