經驗積累之C語言與彙編語言的區別

   從事嵌入式系統開發多年,對於軟件方面,從初期的單片機彙編語言編程,到後來的C++界面程序編寫,已有相當多的經驗累積。正是有了多年的實戰經驗,對於彙編與高級語言在原理及應用等方面形成了自已的一些理解,也是我經常思考的問題,但一直沒有以書面的方式記錄下來,今天之所以寫下這些文字,正是想做一個歸納,日後也好參考。  
    其實,C語言與彙編語言的區別一直是程序員們津津樂道的話題。如果你問一個程序員這樣的問題,他也許會這麼回答你:“C語言可讀性好,代碼便於維護,便於開發;彙編語言編寫的程序不容易看懂,可維護性不好,但是執行效率高。”這樣回答是沒有錯的,但只是一個概括,不夠深入。比方說,彙編語言爲什麼執行效率比C語言高呢?C語言的可讀性又好在哪裏呢?彙編語言不同樣可以用註解來提高可讀性嗎?等等這些的問題。要真正能回答這些問題,不是一件簡單的事情,也不是三言兩語能解釋得清的,需要比較徹底地分析彙編與C的本質上的區別。  

    先說彙編,寫過彙編的程序員都知道,“彙編語言實質上機器語言的助記符。”這句話需要這樣來解析:

    1.CPU只能運行它所支持的指令集,而這些指令集當中的每一條指令都是一些二進制數的序列,也就是“0”和“1”的有序組合;

    2.“0”和“1”的組合不便於程序員的記憶因此有了“MOV A 0x40”等這樣的助記符,也就是說在程序員編寫程序的時候,用“MOV A 0x40”來代替一串“0”和“1”的序列,這樣一看就知道是吧“0x40”單元中的數據搬到累加器A當中來。而如果是用0”和“1”的序列,毫無特徵,很難被程序員記住。

    這也是爲什麼要有彙編語言產生的原因了。  

    以上對彙編語言的解釋基本上就道出了彙編語言的本質,知道了彙編語言的本質,我們不難理解,彙編語言編譯成CPU可執行的機器語言其實只要做一個翻譯的動作就好了,因爲,助記符與對應的二進制指令是一一對應的。進而,我們再來解釋爲什麼彙編語言會比C語言有更高的執行效率。首先,我們要理解一點,類似於C的高級語言面對的對象是程序員,而不是CPU,爲什麼這樣說呢?原因非常簡單,CPU不認識C語言,CPU只認識以“0”“1”形式存在的指令。而C語言的所有語法以及它代碼組織形式都是有助於程序員編寫代碼的。所以,C語言編寫完程序後,需要通過編譯器將C語言編譯成與相應CPU指令集對應的機器語言。問題來了,前面我們說過,彙編語言與機器語言是一一對應的。但是C語言呢?當然沒這麼好事了。C語言的語法是固定的,C語言編寫的程序要編譯成CPU能讀懂的機器語言指令沒辦法一一對應,所以就需要有編譯規則了。比方說一個for循環會有若干條實現對應for循環功能的機器指令對應,而一個switch,也相應會有機器指令段代替。所以C語言最終要編譯機器代碼,必須要遵從許許多多的這樣的規則纔行。我試驗過,用C編寫一個簡單的程序,比方說只包含一個for循環,編譯出的代碼和用匯編寫的最優代碼幾乎是一樣。但代碼量一大,由於受制於規則(不受制也不行呀,否則編不出來),編出來的代碼與用匯編語言寫出來的代碼相比就走了不少“彎路”了。雖然說,現在的很多C編譯器在編譯的時候都會有優化,但是,不可能做得到效率上等同於與機器語言一一對應的彙編語言的效率。畢竟,彙編語言可以理解爲直接就是面對CPU的,只不過是機器語言用助記符代替而矣。  
    以上只是兩種語言效率上區別的一個主要原因,其實,對於資源的利用上,彙編語言同樣有優勢。彙編是直接面對CPU的語言,只要是在指令集支持的範圍內,彙編語言可以直接而靈活地管理包括特殊功能寄存器、通用寄存器、存儲單元的每一個字節,甚至是每一個bit。C語言對內存的使用及管理功能也是很強大的,但畢竟還是受制於語法。舉個最簡單的例子,C語言當中沒有對應三字節或是五字節的變量類型,要麼int型,要麼long型,所以每次申請必須是固定的字節數,勢必造成內存使用上的浪費。而大部份彙編語言根本沒有這樣的語法,在僞指令的幫助下(其實也只是提高可讀性),彙編語言程序可以使用任意字節數的變量,當然處理起來比C語言麻煩得多,最終還是一個字節一個字節地拼接處理,而用C語言寫程序就輕鬆了,不用管這些,最終編譯器會搞定嘛。而輕鬆的代價就是造成了浪費。而內存使用效率不高同時也會影響到整個程序的整體效率。  
    彙編的最後部份,來說明一下僞批令這個東西吧。一個不善於用僞指令寫彙編程序的程序員不是一個好的程序員,這就和寫C語言不用宏是一個道理。僞批令存在價值在於他提高了彙編語言的可讀性,同時也能簡化彙編語言的編程。比方說最通用的創建立即數名稱,而不是用二進制或十六進制數;創建數據表;ARM當中的創建全局及局部變量等。這個不多說了,針對於不同的MCU或CPU有不同的僞指令。  
    再來說說C吧,C語言豐富而實用的語句決定了C語言程序靈活性以及強大的代碼組織能力。利用C語言,我們可以很方便地編寫出龐大的工程,在版本管理工具的幫助下,可以很輕鬆地實現多人協作編程。特別是引入RT-OS以後,C語言的程序框架更加靈活了,添加功能(任務)更加輕鬆。因爲,所有的任務的調控可以直接交給操作系統來做,而程序員需要做的是編寫任務(含一個或多個功能模塊)的內容,以及設置任務的優先級,堆棧數等等。而任務間的通信可以擺脫“全局變量”這個禍害,完全可以通過信號量、郵箱、隊列等形式來溝通。爲什麼說“全局變量”是禍害呢?單程序量不大的時候,“全局變量”可能是好東西,因爲方便嘛,哪都能改它,哪都能讀它。可是,一旦程序大了,源碼文件一多,如果都習慣用全局變量來傳遞及存貯共用量的話,災難就會降臨。你會看到數以千計的全局變量在各各函數間縱橫交錯,如果這些變量不是你創建的,你會很難知道它的作用,因爲系統太大了,它出現的地方太多了,而且,像這樣的變量實在太多,你會因此而感到恐懼,相信很多有經驗的程序員都經歷過吧!然而,這將埋下系統崩潰級別的隱患。因爲,這些全局變量太多,而且出現在太多的地方,很難完全統計出哪些地方可能會修改它們,一量有遺漏,變量的值可能就會和我們想要的值有出入,後果非常嚴重。更有甚者,當全局變量是指向數組的指針或者是數組本身的時候。有的程序員可能對多得數不清的這些變量感到困惑,容易犯的一個錯誤是寫這些全局數組時沒有加以保護,經常都會寫出數組的範圍,而將其它無關的變量給莫名其妙地改了。導致的後果可能是出錯,也有可能是死機,而且,由於這種問題極其隱蔽,很難找出來。  
    所以,在大的系統當中,幸虧有了OS這種東西的存在,它不但能幫我們擺脫全局變量這個禍害,而幫助程序員更加方便地組織各個功能模塊。並且,讓每個任務單一化,進而降低了程序編寫的難度。而用匯編語言編寫較大的工程,是困難重重的事情。首先,必須面對上面提到的全局變量的問題,另外還得面對其它的困難,比方說內存的使用。在C語言裏,程序員只要申請各種類型的變量然後就可以使用了,而具體用的是哪個單元的空間,交給編譯器去管理就好了。而你用匯編語言寫程序的時候,必須要指明所用內存的地址,問題來了,程序員不得不對所有內存的使用瞭如指掌,因爲所有內存單元的使用都必須體現出來,這也是彙編語言的特點。當程序量一旦大到某種呈度的時候,規劃這些內存的使用本身就是一個高難度的工作,因爲同時你還得保證各個地方在使用它們的時候沒有衝突。真的很難,我是有體會的。這時候,我們再想象一下,如果程序太大,我們要幾個人來協作編寫的話,問題就更加複雜了,因爲,沒有了編譯器的幫忙,程序員之間要協商好內存使用的規則,這太難了,因爲面對它們的只不過是一些數量龐大的地址空間,光是劃分區域倒是簡單,但涉及到程序間的交互很大的麻煩就來了,每個程序員必須提供各自的變量接口,因爲彙編的可讀性本來就差,這些接口包括了每一個可能共用的變量(在彙編中只是內存空間資源),以及說明它們的功能,這個工作量非常大,而且一旦做得不好,很容易出錯,出了錯還很難查。用匯編編寫程序還有很多比C困難的地方,再舉一個簡單的例子。彙編語言是低級語言,是機器語言面向程序員的一個一對一的翻譯,所以對於程序員來說,它的功能不夠豐富。在C語言裏寫一個(13200.68/98.56)*256.24的程序,可以直接就表示成"double a; a=(13200.68/98.56)*256.24;",而在彙編裏就沒這麼輕鬆了,彙編裏面一般都沒有直接支持浮點運算的指令,通常情況下都是得專門編寫一個函數來做浮點運算。結果就是彙編程序編寫比C麻煩很多,而且還不直觀。另外,用匯編語言編寫程序對程序員的要求也更高,因爲,程序員必須能撐握CPU或MCU的內存結構、總線結構、功能模塊、堆棧系統、中斷資源及機制等等,否則,是寫不下去的。  
    最後總結,C語言與彙編根本不是一碼事,怎麼可能幾句話就能道出它們的區別呢?就目前的情形來說,由於IC工藝的成熟,MCU的存貯資源越來越便宜,工作頻率也越來越高,所以在資源利用率以及執行效率上沒有像以前要求那麼高了。而且,實現的功能越來越強大,這些因素都助長了C以及C++在嵌入式開發當中地位越來越高。就連MCS-51的程序編寫也以C語言主導了,這還要歸功於KEIL這個強大且十分容易入手的工具。面向程序員的高級語言比面向CPU的彙編語言好用得,在硬件條件允許的情況下,程序員當然選擇用高級語言編程,不旦提高了編程效率,也提高了代碼的可維護性,並且十分有利於編寫大型的工程。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章