中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何實現C++的可移植性和跨平臺開發

發布時間:2020-07-16 11:33:09 來源:億速云 閱讀:715 作者:Leah 欄目:編程語言

本篇文章給大家分享的是有關如何實現C++的可移植性和跨平臺開發,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

概述

  今天聊聊C++的可移植性問題。如果你平時使用C++進行開發,并且你對C++的可移植性問題不是非常清楚,那么我建議你看看這個系列。即使你目前沒有跨平臺開發的需要,了解可移植性方面的知識對你還是很有幫助的。
  C++的可移植性這個話題很大,包括了編譯器、操作系統、硬件體系等很多方面,每一個方面都有很多內容。鑒于本人能力、精力都有限,只能介紹每一個方面最容易碰到的問題,供大伙兒參考。
  后面我會分別從編譯器、C++語法、操作系統、第三方庫、輔助工具、開發流程等方面進行介紹。
編譯器
  在跨平臺的開發過程中,很多問題都和編譯器有關。因此我們先來聊聊編譯器相關的問題。
編譯器的選擇
  首先,GCC是優先要考慮支持的,因為幾乎所有操作系統平臺都有GCC可用。它基本上成了一個通用的編譯器了。如果你的代碼在A平臺的GCC能夠編譯通過,之后拿到B平臺用類似版本的GCC編譯,一般也不會有太大問題。因此GCC是肯定要考慮支持的。
  其次,要考慮是否支持本地編譯器。所謂本地編譯器就是操作系統廠商自產的編譯器。例如:相對于Windows的本地編譯器就是Visual C++。相對于Solaris的本地編譯器就是SUN的CC。如果你對性能比較敏感或者想用到某些本地編譯器的高級功能,可能就得考慮在支持GCC的同時也支持本地編譯器。
編譯警告
編譯器是程序員的朋友,很多潛在的問題(包括可移植性),編譯器都是可以發現并給出警告的,如果你平時注意這些警告信息,可以減少很多麻煩。因此我強烈建議:
1把編譯器的警告級別調高;
2不要輕易忽略編譯器的警告信息。
交叉編譯器
  交叉編譯器的定義參見“維基百科”。通俗地說,就是在A平臺上編譯出運行在B平臺上的二進制程序。假設你要開發的應用是運行在Solaris上,但是你手頭沒有能夠運行Solaris的SPARC機器,這時候交叉編譯器就可以派上用場了。一般情況下都使用GCC來制作一個交叉編譯器,限于篇幅,這里就不深入聊了。有興趣的同學可以參見“這里”。
異常處理
  上一個帖子“語法”由于篇幅有限,沒來得及聊異常,現在把和異常相關的部分單獨拿出來說一下。
小心new分配內存失敗
  早期的老式編譯器生成的代碼,如果new失敗會返回空指針。我當年用的Borland C++ 3.1似乎就是這樣的,現在這種編譯器應該不多見了。如果你目前用的編譯器還有這種行為,那你就慘了。你可以考慮重載new操作符來拋出 bad_alloc異常,便于進行異常處理。
稍微新式一點的編譯器,就不是僅僅返回空指針了。當new操作符發現內存告急,按照標準的規定(參見C++ 03標準18.4.2章節),它應該去調用new_handler函數(原型為typedef void (*new_handler)();)。標準建議new_handler函數干如下三件事:
1、設法去多搞點內存來;
2、拋出bad_alloc異常;
3、調用abort()或者exit()退出進程。
由于new_handler函數是可以被重新設置的(通過調用set_new_handler),所以上述的行為它都可能有。
綜上所述,new分配內存失敗,有可能三種可能:
1、返回空指針;
2、拋出異常;
3、進程立即終止。
如果你希望你的代碼具有較好的移植性,你就得把這三種情況都考慮到。
慎用異常規格
  異常規格在我看來不是一個好東西,不信可以去看看《C++ Coding Standards - 101 Rules, Guidelines & Best Practices》的第75條。(具體有哪些壞處以后專門開一個C++異常和錯誤處理的帖子來聊)言歸正傳,按照標準(參見03標準18.6.2章節),如果一個函數拋到外面的異常沒有包含在該函數的異常規范中,那么應該調用unexcepted()。但是并非所有編譯器生成的代碼都遵守標準(比如某些版本的VC編譯器)。如果你的需要支持的編譯器在異常規范上的行為不一致,那就得考慮去掉異常規范聲明。
不要跨模塊拋出異常
  此處說的模塊是指動態庫。如果你的程序包含有多個動態庫,不要把異常拋到模塊的導出函數之外。畢竟現在C++還沒有ABI標準(估計將來也未必會有),跨模塊拋出異常會有很多不可預料的行為。
不要使用結構化異常處理(SEH)
  如果你從來沒有聽說過SEH,那就當我沒說,跳過這段。如果你以前習慣于用SEH,在你打算寫跨平臺代碼之前,要改掉這個習慣。包含有SEH的代碼只能在Windows平臺上編譯通過,肯定無法跨平臺的。
關于catch(…)
照理說,catch(…)語句只能夠捕獲C++的異常類型,對于訪問違例、除零錯等非C++異常是無能為力的。但是某些情況下(比如某些VC編譯器),諸如訪問違例、除零錯也可以被catch(…)捕獲。所以,你如果希望代碼移植性好,就不能在程序邏輯中依賴上述catch(…)的行為。
硬件體系相關
  這次聊的話題主要是和硬件體系有關的。比如你的程序需要支持不同類型的CPU(x86、SPARC、PowerPC),或者是同種類型不同字長的CPU(比如x86和x86-64),這時候你就需要關心一下硬件體系的問題。
基本類型的大小
  C++中基本類型的大小(占用的字節數)會隨著CPU字長的變化而變化。所以,假如你要表示一個int占用的字節數,千萬不要直接寫“4”(順便說一下,直接寫“4”還犯了Magic Number的大忌,詳見這里),而應該寫“sizeof(int)”;反過來,如果你要定義一個大小必須為4字節的有符號整數,也不要直接用int,要用預先typedef好的定長類型(比如boost庫的int32_t、ACE庫的ACE_INT32、等)。
  差點忘了,指針的大小也有上述的問題,也要小心。
字節序
  如果你沒聽說過“字節序”這玩意兒,請看“維基百科”。通俗地打個比方,在一個大尾序的機器上有一個4字節的整數0x01020304,通過網絡或者文件傳到一臺小尾序的機器上就會變成0x04030201;據說還有一種中尾序的機器(不過我沒接觸過),上述整數會變成0x02010403。
  如果你編寫的應用程序中涉及網絡通訊,一定要在記得進行主機序和網絡序的翻譯;如果涉及跨機器傳輸二進制文件,也要記得進行類似的轉換。
內存對齊
  如果你不曉得“內存對齊”是什么東東,請看“維基百科”。簡單來說,出于CPU處理上的性能考慮,結構體中的數據不是緊挨著的,而是要空開一些間隔。這樣的話,結構體中每個數據的地址正好都是某個字長的整數倍。
  由于C++標準中沒有定義內存對齊的細節,因此,你的代碼也不能依賴對齊的細節。凡是計算結構體大小的地方,都老老實實寫上sizeof()。
  有些編譯器支持#pragma pack預處理語句(可以用來修改對齊字長),不過這種語法不是所有編譯器都支持,要慎用。
移位操作
  對于有符號整數的右移操作,有些系統默認使用算數右移(最高的符號位不變),有些默認使用邏輯右移(最高的符號位補0)。所以,不要對有符號整數進行右移操作。順便說一下,即使沒有移植性問題,代碼中也盡量少用移位運算符。那些企圖用移位運算來提高性能的同學更要注意了,這么干不但可讀性很差,而且吃力不討好。只要不太弱智的編譯器,都會自動幫你搞定這種優化,無須程序員操心。
操作系統
  上一個帖子提到了“硬件體系”相關的話題,今天來說說和操作系統相關的話題。C++跨平臺開發中和OS相關的瑣事挺多,所以今天會啰嗦比較長的篇幅,請列位看官見諒 :-)
  為了不繞口,以下把Linux和各種Unix統稱為Posix系統。
文件系統(FileSystem以下簡稱FS)
  剛開始搞跨平臺開發的新手,多半都會碰上和FS相關的問題。所以先來聊一下FS。歸納下來,開發中容易碰上的FS差異主要有如下幾個:目錄分隔符的差異;大小寫敏感的差異;路徑中禁用字符的差異。
  為了應對上述差異,你要注意如下幾點:
1、文件和目錄命名要規范
  在給文件和目錄命名時,盡量只使用字母和數字。不要在同一個目錄下放兩個名稱相似(名稱中只有大小寫不同,例如foo.cpp與Foo.cpp)的文件。不要使用某些OS的保留字(例如aux、con、nul、prn)作文件名或目錄名。
  補充一下,剛才說的命名,包括了源代碼文件、二進制文件和運行時創建的其它文件。
2、#include語句要規范
  當你寫#include語句時,要注意使用正斜線“/”(比較通用)而不要使用反斜線“\”(僅在Windows可用)。#include語句中的文件和目錄名要和實際名稱保持大小寫完全一致。
3、代碼中涉及FS操作,盡量使用現成的庫
  已經有很多成熟的、用于FS的第三方庫(比如boost::filesystem)。如果你的代碼涉及到FS的操作(比如目錄遍歷),盡量使用這些第三方庫,可以幫你省不少事情。
★文本文件的回車CR/換行LF
  由于幾個知名的操作系統對回車/換行的處理不一致,導致了這個煩人的問題。目前的局面是:Windows同時使用CR和LF;Linux和大部分的Unix使用LF;蘋果的Mac系列使用CR。
  對于源代碼管理,好在很多版本管理軟件(比如CVS、SVN)都會智能地處理這個問題,讓你從代碼庫取回本地的源碼能適應本地的格式。
  如果你的程序需要在運行時處理文本文件,要留意本文方式打開和二進制方式打開的區別。另外,如果涉及跨不同系統傳輸文本文件,要考慮進行適當的處理。
  ★文件搜索路徑(包括搜索可執行文件和動態庫)
  在Windows下,如果要執行文件或者加載動態庫,一般會搜索當前目錄;而Posix系統則不盡然。所以如果你的應用涉及到啟動進程或加載動態庫,就要小心這個差異。
  ★環境變量
  對于上述提到的搜索路徑問題,有些同學想通過修改PATH和LD_LIBRARY_PATH來引入當前路徑。假如使用這種方法,建議你只修改進程級的環境變量,不要修改系統級的環境變量(修改系統級有可能影響到同機的其它軟件,產生副作用)。
  ★動態庫
  如果你的應用程序使用動態庫,強烈建議動態庫導出標準C風格的函數(盡量不要導出類)。如果在Posix系統中加載動態庫,切記慎用RTLD_GLOBAL標志位。這個標志位會Enable全局符號表,有可能會導致多個動態庫之間的符號名沖突(一旦碰到這種事,會出現匪夷所思的運行時錯誤,極難調試)。
  ★服務/看守進程
  如果你不清楚服務和看守進程的概念,請看維基百科(這里和這里)。為了敘述方便,以下統稱服務。
  由于C++開發的模塊大部分是后臺模塊,經常會碰到服務的問題。編寫服務需要調用好幾個系統相關的API,導致了與操作系統的緊密耦合,很難用一套代碼搞定。因此比較好的辦法是抽象出一個通用的服務外殼,然后把業務邏輯代碼作為動態庫掛載到它下面。這樣的話,至少保證了業務邏輯的代碼只需要一套;服務外殼的代碼雖然需要兩套(一個用于Windows、一個用于Posix),但他們是業務無關的,可以很方便地重用。
  ★默認棧大小
  不同的操作系統,棧的默認大小差別很大,從幾十KB(據說Symbian只有12K,真摳門)到幾MB不等。因此你事先要打聽一下目標系統的默認棧大小,如果碰上像Symbian這樣摳門的,可以考慮用編譯器選項調大。當然,養成“不在棧上定義大數組/大對象”的好習慣也很重要,否則再大的棧也會被撐爆的。
多線程
  最近一個多月寫的帖子比較雜,導致本系列又好久沒更新了。結果又有網友在評論中催我了,搞得我有點囧。今天趕緊把多線程篇補上。上次聊操作系統 的時候,由于和OS有關的話題比較瑣碎,雜七雜八說了一大堆。當時一看篇幅有點長,就把多進程和多線程的部分給留到后面了。
  ★編譯器
◇關于C運行庫選項
  先來說一個很基本的問題:關于C運行庫(后面簡稱CRT:C Run-Time)的設置。本來不想聊這么低級的問題,但周圍有好幾個人都在這個地方吃過虧,所以還是講一下。
  大部分C++編譯器都會自帶有CRT(可能還不止一個)。某些編譯器自帶的CRT可能會根據線程的支持分為單線程CRT和多線程CRT兩類。當你要進行多線程開發的時候,別忘了確保相關的C++工程項目使用的是多線程的CRT。否則會死得很難看。
  尤其當你使用Visual C++創建工程項目,更加要小心。如果新建的工程項目是不含MFC的(包括Console工程和Win32工程),那工程的默認設置會是使用“單線程CRT”,如下圖所示:
◇關于優化選項
  “優化選項”是另一個很關鍵的編譯器相關話題。有些編譯器提供號稱很牛X的優化選項,但是某些優化選項可能會有潛在的風險。編譯器可能自作主張打亂執行指令的順序,從而導致出乎意料的線程競態問題(Race Condition,詳細解釋看“這里 ”)。劉未鵬同學在“C++多線程內存模型 ”里舉了幾個典型的例子,大伙兒可以去瞧一瞧。
  建議只使用編譯器常規的速度優化選項即可。其它那些花哨的優化選項,增加的效果未必明顯,但是潛在的風險不小。實在不值得冒險。
  以GCC為例:建議用-O2 選項即可(其實-O2 是一堆選項的集合),沒必要冒險用-O3 (除非你有很充足的理由)。除了-O2 和-O3 之外,GCC還有一大坨(估計有上百個)其它的優化選項。如果你企圖用當中的某個選項,一定要先把它的特性、可能的副作用都摸清楚,否則將來死都不知道怎么死的。
  ★線程庫的選擇
  由于當前的C++ 03標準幾乎沒有涉及線程相關的內容(即使將來C++ 0x包含了線程的標準庫,編譯器廠商的支持在短期內也未必全面),所以在未來很長的一段時間,跨平臺的多線程支持還是要依賴第三方庫。所以線程庫的選擇是大大滴重要。下面大致介紹一下幾個知名的跨平臺線程庫。
  ◇ACE
  先說一下ACE這個歷史悠久的庫。如果你之前從未接觸過它,先看“這里 ”掃盲。從ACE的全稱(Adaptive Communication Environment)來看,它應該是以“通訊”為主業。不過ACE對“多線程”這個副業的支持還是非常全面的,比如互斥鎖(ACE_Mutex)、條件變量(ACE_Condition)、信號量(ACE_Semaphore)、柵欄(ACE_Barrier)、原子操作(ACE_Atomic_Op)等等。對某些類型比如ACE_Mutex還細分為線程讀寫鎖(ACE_RW_Thread_Mutex)、線程遞歸鎖(ACE_Recursive_Thread_Mutex)等等。
  除了支持很全面,ACE還有另一個很明顯的優點,就是對各種操作系統平臺及其自帶的編譯器支持很好。包括一些老式的編譯器(比如VC6),它也能夠支持(此處所說的支持 ,不光是能編譯通過,而且要能穩定運行)。這個優點對于跨平臺開發那是相當相當滴明顯。
  那缺點捏?由于ACE開工的年頭很早(大概是上世紀九十年代中期),那會兒很多C++的老特性都還沒出來(更別提新特性了),所以感覺ACE整個的風格比較老氣,遠不如boost那么時髦前衛。
  ◇boost::thread
  boost::thread正好和ACE形成鮮明對照。這玩意貌似從boost 1.32版本開始引入,年頭比ACE短。不過得益于boost里一幫大牛的支持,發展還是蠻快的。到目前的boost 1.38版本,也能夠支持許多特性了(不過似乎沒ACE多)。鑒于很多C++標準委員會的成員云集在boost社區中,隨著時間的推移,boost::thread終將成為C++線程的明日之星,前途無量啊!
  boost::thread的缺點就是支持的編譯器不夠多,尤其是一些老式 編譯器(很多boost的子庫都有此問題,多半因為用了一些高級的模板語法)。這對于跨平臺而言一個比較明顯的問題。
  ◇wxWidgets 和QT
  wxWidgets和QT都是GUI界面庫,但是它們也都內置和對線程的支持。wxWidgets線程的簡介可以看“這里 ”,關于QT線程的簡介可以看“這里 ”。這兩個庫對線程的支持差不多,都提供了諸如mutex、condition、semaphore等常用的機制。不過特性沒有ACE豐富。
  ◇如何權衡
  對于開發GUI軟件并已經用上了wxWidgets或者QT,那你可以直接用它們內置的線程庫(前提是你只用到基本的線程功能)。由于它們內置的線程庫,特性稍嫌單薄。萬一你需要某高級的線程功能,那得考慮替換成boost::thread或ACE。
  至于boost::thread和ACE的取舍,主要得看軟件的需求了。如果你要支持的平臺挺多挺雜,那建議選用ACE,以免碰上編譯器不支持的問題。如果你只需要支持少數幾個主流的平臺(比如Windows、Linux、Mac),那建議用boost::thread。畢竟主流操作系統上的編譯器,對boost的支持還是蠻好的。
  ★編程上的注意事項
  其實多線程開發,需要注意的地方挺多的,我只能大致列幾個印象比較深的注意事項。
  ◇關于volatile
  說到多線程編程可能碰到的陷阱,那就不得不提到volatile 關鍵字。如果你對它還不甚了解,先看“這里 ”掃盲一下。由于C++ 98和C++ 03標準都沒有定義多線程的內存模型,而標準中也就volatile 和線程沾點兒邊。結果導致C++社區中有相當多的口水都集中在volatile 身上(其中有不少C++大牛的口水)。有鑒于此,我這里就不再多啰嗦了。推薦幾個大牛的文章:Andrei Alexandrescu 的文章“這里 ”、還有Hans Boehm的文章“這里 ”和“這里 ”。大伙兒自個兒去拜讀一下。
  ◇關于原子操作
  有些同學光知道多個線程的競爭寫 需要加鎖,卻不知道多個讀 單個寫 也需要保護。比如有某個整數int nCount = 0x01020304;在并發狀態下,一個寫線程去修改它的值nCount = 0x05060708;另一個讀線程去獲取該值。那么讀線程有沒有可能讀取到一個“壞”的(比如0x05060304)數據捏?
  數據是否壞掉,取決于對nCount的讀和寫是否屬于原子操作。而這就依賴于很多硬件相關的因素了(包括CPU的類型、CPU的字長、內存對齊的字節數等)。在某些情況下,確實可能出現數據壞掉。
  由于我們討論的是跨平臺的開發,天曉得將來你的代碼會在啥樣的硬件環境下執行。所以在處理類似問題的時候,還是要用第三方庫提供的原子操作類/函數(比如ACE的Atomic_Op)來確保安全。
  ◇關于對象的析構
在之前的系列帖子“C++對象是怎么死的? ”里面,已經分別介紹了Win32平臺和Posix平臺下線程的非自然死亡問題。
由于上述幾個跨平臺的線程庫底層還是要調用操作系統自帶的線程API,所以大伙兒還是要盡最大努力確保所有線程都能夠自然死亡。

以上就是如何實現C++的可移植性和跨平臺開發,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

油尖旺区| 湘阴县| 星子县| 澜沧| 延川县| 岗巴县| 荔波县| 永兴县| 钦州市| 河津市| 思茅市| 奈曼旗| 东兰县| 布尔津县| 古田县| 大冶市| 革吉县| 抚松县| 临泉县| 济宁市| 大名县| 海淀区| 闻喜县| 和静县| 高雄市| 内江市| 雅安市| 东平县| 汾西县| 贡山| 龙门县| 安徽省| 千阳县| 信阳市| 万荣县| 石渠县| 建平县| 新宁县| 和田市| 浙江省| 揭阳市|