曾傳新
首先我們糾正一下題目中一個小小的誤區,不是C和C++不能跨平臺,應該是說是C/C++源代碼在編譯後生成的
.exe
文件不能跨平臺,源代碼和可執行文件要區分開來。想要搞明白這個問題,我們先得了解一下源代碼是怎麼變成程序的。
四個過程:預處理——編譯——彙編——可執行文件
當我們編寫完代碼後,源代碼會經過上述的四個環節,最終變成常見的可執行文件。
預處理階段(hello.i):在源代碼中會有頭文件,一些宏,註釋等。預處理的目的就是將頭文件展開,宏文件代換,去掉註釋等,對代碼進行一些初步的處理
編譯階段(hello.s):這一階段主要是檢查語法上的錯誤,比如內存有沒有溢出,指針有沒有指錯對象,然後生成可彙編文件。
彙編階段(hello.o):計算機是不認識代碼的,所以需要將彙編代碼轉換成0和1組成的機器碼
鏈接(a.out):鏈接有兩種情況:靜態和動態。靜態庫和應用程序編譯在一起,在任何情況下都能運行;而動態庫是動態鏈接,文件生效時才會調用。最終生成一個可執行文件。
編譯器的作用
我們不要把編譯器想的太厲害,覺得編譯器是萬能的。實際上編譯器就像是一個翻譯,負責把高級語言轉變成機器能看懂的低級語言,翻譯過程就是上述的四個過程。但是其中有一點需要格外注意。那就是不同的公司使用的指令集不同。輸出程序的格式和CPU使用的指令集有關,比如X86,arm,還有MIPS等等, 由於設計思路的差異,所以不同平臺上編譯生成後的可執行文件格式是不一樣的,可能在ubantu裡能運行的C程序,放到windows下就會報錯。
類比一下java,為什麼說java可以跨平臺,是因為java內置了一個虛擬機,程序都從虛擬機中跑的,所以有人說“java不僅是一種語言,更是一個平臺”。
綜上所述,C/C++的一些基礎性代碼是可以跨平臺的(可能會受API影響),是生成的可執行文件不能跨平臺,C/C++不自帶編譯器,不同平臺下的編譯器存在差異。
(都看到最後了,麻煩點個贊和關注吧,謝謝~)
愛思考的奧特曼
先來討論一下C&C++語言的執行過程,從而搞清楚為什麼C&C++語言不能跨平臺。
我們分階段來討論(如圖4所示):
- 預處理階段。預處理器(cpp)來把 代碼中開頭的行進行展開, 比如頭文件,宏等內容,修改最初的C文件。
- 編譯階段。編譯器(ccl)將修改後的C文件,翻譯成了 另一文本文件,,這就是我們所說的彙編程序了。
- 彙編階段。彙編器(as)將翻譯成機器語言指令。 把這些命令打包成一種叫做可重定位目標程序(relocatable object program)的格式,此時的輸出格式就是了。這其實就是二進制文件了。
- 鏈接階段。編譯過程最後還有一個鏈接階段(程序調用了 函數),最後的輸出結果還是和上一步類似,都是直接二進制文件。
比如 計算 1+1,兩個 數據1都 使用 來表示,而 加操作,放在cpu中,可以是 (這個是胡亂寫的),這個二進制代表的加操作能被計算機識別。而因為這個加操作對於cpu來說,編碼的格式是固定的。所以可以直接一個助記符來表示,這樣科學家們寫程序就方便多了,而這就是彙編程序的由來。因為彙編程序完成之後,可以再有一個專門的程序(就是要上文中所說的彙編器)來把編寫的彙編程序編譯成0和1.這樣計算機也可以識別了,而彙編語言本身也方便了程序的編寫和閱讀。
編寫彙編比直接編寫二進制方便高效了太多。但是 隨著計算任務的複雜,程序的規模越來越龐大,使用匯編程序也很累啊,那麼是否有更簡單的方式呢?所以科學家們發明了高級語言(比如 ,等),在編寫程序的時候,使用C語言等編寫,然後再使用 編譯器將C語言程序翻譯成彙編程序,彙編程序再使用彙編器編譯成0和1,這樣,cpu能識別的東西沒有變化,但是對於編寫程序的人,確實方便了很多。
通過以上的描述,我們就知道了高級語言的大概由來。也明白了我們所編寫的各種高級語言,到了最後,其實都是轉化為二進制執行。
而直接二進制格式的程序,我們稱之為本地機器碼(native code)。而類似那些 之類的 助記符,以及彙編的編寫格式或標準,我們稱之為 指令集。
但是問題的關鍵來了。不同公司所生產的 cpu芯片。他們所使用的指令集不同啊, 這種芯片設計的事情,沒有國際統一的標準,甚至像intel所代表的複雜指令集,和arm為代表的精簡指令集,還有PowerPC、MIPS等指令集,它們指令集的設計思路就是不一樣的。
所以為什麼說C&C++語言不能實現跨平臺運行,就是因為它編譯出來 輸出文件的格式,只適用於某種特定的CPU,而其他CPU不認識啊。
鼴鼠科技
程序的可移植性,本質上與實現它的編程語言無關。需要區分兩個概念:編程語言和語言的實現。
編程語言
我們通常說的C/C++、Java、Python、JavaScript等編程語言,其實是指語法和語義上的一種規範。這種規範,定義了編寫程序時必須要遵循的一種書寫規則,只有符合規則才能被編譯器正確識別和理解。
語言的實現
語言的實現,通常指的就是編譯器了。對於同一種語言,不同的編譯器可以有完全不同的實現方式。
以Python語言為例,就有很多種大相徑庭的編譯器實現。
- CPython - 解釋執行。
- PyPy - JIT執行。
- Jython - 把Python源碼編譯成Java字節碼,然後再Java虛擬機裡執行。
C/C++也是一樣,有很多種不同的實現,不過目前主流的實現,全都是編譯成二進制的機器碼執行的。但是,其實網絡上也有很多C/C++的解釋器,就是直接把C/C++的源碼解釋執行的。
我目前正在開發中的一個編譯器項目,就支持C語言的絕大部分語法,目前的實現,也是解釋執行的。
瞭解了編程語言和語言的實現後,再來說說程序的可移植性。
程序的可移植性
程序的可移植性,通俗來講,就是你在一個環境上編寫出來的代碼,是不是能夠很方便的把它放到另外一個環境上運行。這裡的環境,包括軟件環境和硬件環境。
- 軟件環境:通常指操作系統,以及程序所依賴的運行時環境,如系統支持庫等。
- 硬件環境:指程序運行的目標硬件,包括CPU、芯片組、外設等。
但從編程語言本身來說,理論上,任何高級編程語言都是可移植的,只不過實現可移植性的方式有所不同。
編寫一次,到處編譯
對於C/C++,目前幾乎所有通用的實現都是直接把源碼編成二進制目標代碼。對於這種實現方式,要想進行移植,就必須要保證在不同的軟件環境和硬件環境下,都要實現相應的編譯器。比如要有Windows+x86的編譯器、Linux+x86的編譯器、Linux+ARM的編譯器等等。也就是編寫一次,到處編譯。
所以,所謂的C/C++程序不能跨平臺,是指被編譯成二進制的可執行文件不能跨平臺,而不是C/C++語言本身不能跨平臺。
編譯一次,到處運行
而Java則選擇了不同的實現方式。Java典型的實現是,把Java源碼編譯成字節碼,然後把字節碼放到虛擬機中進行執行。
對這種方式,要實現可移植性,則必須要保證,在每種不同的軟件環境和硬件環境中,都必須要實現相應的Java虛擬機。
比如要有Windows+x86的Java虛擬機,Linux+x86的Java虛擬機、Linux+ARM的Java虛擬機等。
這些不同的虛擬機,都遵循相同的Java字節碼的規範。因此,只要把Java源代碼編譯成標準的Java字節碼,就可以在所有的這些虛擬機中運行。
對編譯器、OS內核、性能調優、虛擬化等技術感興趣的朋友,歡迎關注!