熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> ASP編程 >> 正文

用Visual C++ 2005編寫更快的代碼

2013-11-15 12:52:14  來源: ASP編程 

  對於C++語言的愛好者來說Visual Studio NET 中C++編譯器的引入絕對令人垂涎欲滴Visual C++ NET 中有%的部分與ISO C++標准保持一致這使它比以往任何版本更為靠近這些標准而且它還加入了對一些功能(如局部模板專用化)的語言支持它還包括增強的緩沖區安全檢查和改進的編譯器診斷功能C++開發人員就像C#和Visual Basic NET開發人員一樣可以使用拖放窗體設計器來構建健壯的Windows窗體應用程序該編譯器還包含了針對Intel Pentium 和AMD Athlon處理器的優化

  如果您對Visual C++ NET 感到興奮不已您將會更加瘋狂地愛上它的下一個版本Visual C++ Visual C++ NET開發提供了既優雅又強大的新語法支持全新的優化技術已經使Microsoft產品的運行速度提高了%它通過新的編譯模式來確保Microsoft NET Framework通用語言基礎結構(Common Language InfrastructureCLI)的一致性和可驗證性並且具有新的互操作(interop)模型這不僅提供了本機和托管環境的無縫融合而且還在跨邊界的情況下提供了完全控制該編譯器增強了前兩個版本中提供的緩沖區安全檢查選項並且還包括了C++應用程序普遍使用的以安全性為中心的庫的新版本它提供了對OpenMP標准以及位平台(其中包括Intel Itanium和AMD芯片)的支持它解決了混合DLL加載問題並且提供了對Double P/Invoke性能問題的運行時的自動清除還可以列出許多增強和改進正如C++小組的一位架構師告訴我的兄弟C++總算找到了屬於自己的位置!

  C++/CLI新的語法

  在我們這些人中有多少人討厭使用前兩個版本C++的托管擴展語法並且認為其中盡是錯誤?有多少人認為Visual C++沒有被當作基於NET的頭號語言?很明顯當中的大多數人都是這樣的(其中包括開發團隊本身只要閱讀一下他們的Blog就知道了)Visual C++小組的人聽到了這些抱怨於是開始開發Visual C++與Visual Studio NET 一起引入的C++語法的托管擴展就像恐龍一樣消失殆盡了因為引入了修訂的語言定義從而產生了一種有吸引力的新語法

  設計小組對於這個版本在語言設計方面有幾個重要的目標首先(可能對那些認為代碼是一種藝術的人來說最為重要)他們想要確保編程人員在編寫C++代碼時感到很自然而且通過對ISO C++標准的純粹擴展可以提供一種優雅的語法他們想要讓編程人員輕松地使用C++編寫可驗證的代碼來支持部分信任的情況例如SQL Server 中的ClickOnce部署窗體設計支持和托管代碼宿主他們不想為任何比C++更低級的語言提供任何空間他們想把NET的全部強大功能帶給C++而與此同時也把C++的強大功能帶給NET他們在各個方面都取得了驕人的成功

  新的擴展規范叫做C++/CLI並且現在正在進行標准化的工作要體驗一下新的語言擴展請參見於日在線公布的候選基本文檔

  對任何閱讀采用新語法編寫的代碼的人來說最容易注意到曾經在托管擴展中以雙下劃線關鍵字定義垃圾回收類屬性等的流行做法已經成為過去雖然這樣一些關鍵字仍然保留著並且還加入了一些新的關鍵字但它們現在已經不經常使用了並且也不會影響到代碼的可讀性這些雙下劃線的關鍵字由兩種新類型的關鍵字來代替上下文敏感的關鍵字和間隔排列的關鍵字上下文敏感的關鍵字是只有在特定上下文中才使用的關鍵字而間隔排列的關鍵字是在與其他關鍵字組合時才使用的關鍵字例如托管擴展中的__property關鍵字會被property關鍵字取代(不僅如此用來定義一個屬性及其訪問器的全部語法都有了顯著的改進使得聲明看起來非常類似於用C#編寫的代碼請參見圖中的示例)這並不影響在編碼時將property用作變量的名稱在聲明某一類型的屬性這一上下文中被解析為property的標記僅被視為一個關鍵字

   語法對比

  托管擴展語法
public __gc __sealed class Student
{
    private:
        double m_grade;
        String* m_name;
    public:
        __property double get_Grade() { return m_grade; }
        __property void set_Grade(double newGrade) { m_grade = newGrade; }

  __property String* get_Name() { return m_name; }
        __property void set_Name(String* newName) { m_name = newName; }
}
C++/CLI 語法
public ref class Student sealed
{
    private:
        double m_grade;
    public:
        // standard property syntax
        property double Grade
        {
            double get() { return m_grade; }
            void set(double newGrade) { m_grade = newGrade; }
        }

  // trivial property
        // compiler can generate accessors and backing store
        property String^ Name;
}

  在新的語法中類型以形容詞類的形式聲明其中形容詞描述您正在創建的類是什麼類型如下所示
class            N  { /*…*/ };  // native type
ref class        R  { /*…*/ };  // CLR reference type
value class      V  { /*…*/ };  // CLR value type
interface class  I  { /*…*/ };  // CLR interface type
enum class       E  { /*…*/ };  // CLR enumeration type

  在之前的語言版本中類型被聲明時就可以確定它的使用范圍及方式只有本機類或結構和托管值類型可以在堆棧上創建托管引用類總是存在於托管堆當中在Visual C++ 所有的類型無論是本機的還是托管的都在堆棧上創建它使用基於堆棧的確定性清理語義來完成這一功能

  要在本機堆上實例化類型T的一個對象可以使用new T這樣就可以返回一個指向本機堆上的對象地址的指針(一個在Visual Studio NET 和Visual Studio NET 中稱為__nogc指針概念)為了在托管堆上實例化類型T的一個對象Visual C++ 引入了gcnew這一關鍵字它與new關鍵字的使用方式相同調用gcnew T可以返回指向托管堆中整個對象的一個句柄句柄(handler)是在Visual C++ 中引入的一個新構造它類似於托管擴展中的__gc指針要在堆棧上實例化T類型的對象標准的T t;聲明就已經足夠了

  為了公平起見還是介紹一下是如何定義實例化的托管引用類總是存在於托管堆當中而本機類型總是存在於堆棧或本機堆當中當一個托管引用被聲明為存在於堆棧上時編譯器實際上還會在托管堆上對其進行實例化

  這樣會帶來一些問題當在堆棧上的實例超出它的使用范圍時會怎樣?這個實例將如何被清理掉?許多C#開發人員一直在抱怨C#語言缺少確定性清理C#語言提供using關鍵字來簡化IDisposable對象的處置但這需要額外的代碼而且與C++開發人員所熟悉的析構函數的模式相比顯得尤為笨拙在C#中安全的清理工作在默認情況下是無法進行的它需要進行顯式的編碼例如請考慮圖中的第一個C#代碼片斷StreamReader對象是在托管堆上聲明的當這個方法執行完畢之後StreamReader的實例就沒有任何引用存在了然而直到垃圾回收器運行時這個對象才會被清理掉直到那時所用的文件才會被關閉而在此之前應用程序會一直占用其打開的文件句柄要添加確定性清除必須使用由利用非托管資源的類實現的IDisposable接口

  圖中的第二個代碼示例顯示了C#中的新代碼的外觀其實這種方法也未嘗不可而且也還算有一定的可讀性但當開始加入更多需要清理的對象時您的代碼就會變得越來越難懂而且任何您忘記清理的對象都會在最後垃圾回收器實際運行時為finalizer線程增加負擔而與此同時也許已經鎖定了一些有價值的資源這一點在查看Visual Basic NET中的同等實現時顯得尤為不堪同樣如圖所示(盡管Visual Basic 增加了與C#相類似的using語句)

   確定性清理
實現 代碼
沒有確定性清理的 C# string ReadFirstLineFromFile(string path)
{
  StreamReader reader = new StreamReader(path);
  return readerReadLine();
})
具有確定性清理的 C# string ReadFirstLineFromFile(string path)
{
  using (StreamReader reader = new StreamReader(path))
  {
    return readerReadLine();
  }
}
具有確定性清理的 Visual Basic NET Function ReadFirstLineFromFile( _
    ByVal path As String) As String
  Dim reader As StreamReader
  Try
    reader = New StreamReader(path)
    ReadFirstLineFromFile = readerReadLine()
  Finally
    If Not reader Is Nothing Then _
      CType(reader IDisposable)Dispose()
  End Try
End Function
具有確定性清理的 C++ String^ ReadFirstLineFromFile(String^ path)
{
  StreamReader reader(path);
  return readerReadLine();
}

  現在Visual C++ 在任何類型上都提供了可以具有析構函數/finalizer的功能無論這種類型是托管的還是本機的在它為托管類型的情況下編譯器會將析構函數映射到IDisposableDispose方法這意味著能夠用C++語言編寫同樣的方法如圖中的第四個代碼片斷所示其中閱讀器的析構函數/Dispose方法將會自動被調用就像在C#中使用using語句一樣當在堆棧上創建某一類型時它的析構函數會在它超出其使用范圍時被調用

  托管擴展的一個最大的問題是對指針的使用指針被用於各種各樣的任務而其情況也是復雜多變的因而非常難以理解在某一特定的代碼段中要解讀自己在和哪一種指針打交道需要有一定程度的天賦這種復雜性在下一個版本中會被去掉在Visual C++ 指針還是原原本本的C++指針它們指向穩定的對象而您則可以用指針進行算術操作指向對象的指針的生命周期必須由開發人員顯式管理當使用指針時運行庫不會負責對指針帶來的垃圾進行清理

  現在讓我們看一下Visual C++ 的設計人員是如何解決這一問題的與Visual Studio NET 和Visual Studio 中使用new運算符返回指針不同gcnew運算符返回一個句柄這是一種新構造在語法中用^符號來表示該句柄引用托管堆中的整個對象也就是說它們不能用來指向類型的內部而編譯器對它們的使用有許多限制以此來強制執行這種行為而這也可以幫助開發人員正確並安全地使用句柄句柄不允許進行指針算術運算也不可以被強制轉換為空指針或是任何整數類型然而星號和箭頭運算符仍被用來取消對它的引用

  這並不意味著您不能再獲得一個指向垃圾回收堆上的指針與在C#中組合&運算符與固定的關鍵字相似在Visual C++ pin_ptr抽象類型允許您檢索指向托管堆上對象的釘住指針只要這個指針存在托管堆中的對象就會被釘住這可以防止垃圾回收器在回收的過程中移動它Visual C++ 還引入了跟蹤引用運算符用百分號(%)來表示當在C++中引入本機的&引用運算符時大多數開發人員都知道可以把它理解成一個指向對象的指針在使用時是由編譯器來自動清除的在大多數情況下%對^而言就像&對*一樣

  在托管的環境下將本機引用指向托管對象就像將本機指針指向托管對象一樣危險在指針與引用幕後的基本原理就是被引用的對象並不會被四處移動跟蹤引用和本機引用很相似唯一例外的是跟蹤引用引用托管堆上的對象並且對其進行跟蹤即便是它們被垃圾回收器移走百分號運算符也被用來提取托管對象的地址所以就像&運算符在應用於本機類型時返回指向該對象的指針一樣%運算符在應用於托管引用類型時會返回一個指向該對象的句柄

  一般來說當C++開發人員知道標准在控制它們的語言時他們會感到心安理得由於這個原因為了促進第三方的采用並確保語言向前發展的穩定性這種新的語法采集眾長而成為一個稱為C++/CLI的提議標准ECMA選舉出了一個特別工作組名為TG致力於分析和采用這一標准就像WG作為ISO C++的管理團體一樣實際上WG中的關鍵人物也在TG中工作他們的計劃是在年年底將其C++/CLI標准化

  互操作選項

  在Visual Studio NET 的所有基於NET框架的語言中Visual C++ 提供了最好的互操作功能它具有實現實際的互操作方案所必需的功能Quake II到NET框架的移植便是例證具體細節請訪問Visual C++ 進一步擴展了這一功能

  在托管與本機環境中使用NET 互操作有四種主要途徑COM 互操作可以使用Runtime Callable Wrappers(RCW)與COM Callable Wrappers(CCW)來實現通用語言運行庫(CLR)負責類型封送(除非在極少的情況下使用自定義封送拆收器)並且這些調用的開銷很大需要非常小心地盡量避免接口往來過於頻繁否則就會出現很嚴重的性能問題還需要保證這些包裝一直與其底層的組件保持一致也就是說在簡單的互操作場景而試圖使用大量的本機COM代碼時COM 互操作非常有用

  第二種互操作選擇是使用P/Invoke要達到此目的可以使用DLLImport屬性並且在方法聲明中為想要導入的函數指定屬性封送是按照它在聲明中的指定方式來處理的然而只有在有代碼需要通過DLL導出公開必須的功能時DLLImport才是有用的

  當需要從本機代碼調用托管代碼時CLR宿主也是一種選擇在這種情況下本機應用程序必須驅動所有的執行設置主機綁定到運行庫啟動主機檢索適當的應用程序域設置調用上下文查找所需的程序集和類並調用所需類上的操作在控制發生什麼以及何時發生方面這無疑是最健壯的解決方案之一但這也會帶來讓人難以置信的枯燥並需要許多自定義代碼

  第四種選擇也有可能是最簡單並最可行的選擇就是使用C++的互操作功能通過設置/clr開關編譯器會生成中間代碼(MSIL)而不是本機代碼唯一被生成為本機代碼的是那些無法被編譯成中間代碼的代碼其中包括帶有內聯asm塊的函數以及使用像Streaming SIMD Extensions (SSE)這樣一些特定於CPU的固有特性的操作Quake II就是使用/clr開關移植到NET的Vertigo軟件小組花費了一天的時間將原來由C編寫的游戲代碼成功地編譯成C++代碼然後設置了/clr開關他們的代碼很快就可以運行在NET框架上在不添加任何附加的二進制文件而只是簡單地加入適當的頭文件的情況下托管C++和本地C++可以相互調用而無需部分開發人員做一些額外的工作編譯器負責創建適當的)轉換代碼來往返在兩種環境之間

  這給C++開發人員帶來了一些問題問題之一就是現在聲名狼籍的混合DLL加載問題Visual Studio NET 和Visual Studio NET 的用戶都受此問題的影響如果正在運行加載器鎖(Loader Lock)內的本機代碼並且引用一個還沒有加載的程序集中的托管類型CLR會非常友善地加載這一程序集它是通過調用LoadLibrary來實現的當然LoadLibrary會嘗試獲得加載器鎖這會碰到死鎖問題開發人員和產品經理如果聽說這個問題在即將推出的版本中會得到解決一定非常高興

  /clr開關對C++開發人員來說是一個極好的工具但它也有一些缺點正如本文之前提到的一樣由/clr開關產生的映像既包含本機代碼又包含托管代碼這有時會導致問題的出現首先這些混合映像並不是遵循CLI的(這意味著例如它們將無法在Rotor上運行)它們有本機的入口點而當頻繁跨越托管邊界時會帶來極大的轉換開銷但最重要的是這些本機入口點的存在會對使用包括反射在內的程序集的工具帶來極大的危害為了使用反射來檢查一個映像必須首先加載程序集並執行它只有在所有的初始化都執行完畢時反射才能檢查元數據遺憾的是反射無法正確地加載包含有本機入口點的托管程序集

  此外Visual Studio NET 很少生成可驗證的代碼即使它這樣做它花費在處理一些其他重要問題上的時間也會比較多而中間代碼對無法驗證的指令有著一流的支持(可以進行指針算術運算執行間接加載和訪問本機堆)可驗證的代碼能夠處理一些需要部分信任的情況而這又可以支持Visual Studio 提供一些豐富功能ClickOnce部署依賴於部分信任與SQL Server 中的托管代碼宿主一樣Visual C++ 開發小組的主要目標之一就是讓編譯器能夠在開發人員開發非混合和可驗證的映像產品時有所幫助它們通過引入兩個新的編譯器開關來實現這一點/clrpure和/clrsafe不過在深入講解如何使用這些新開關之前需要分析一下C++ 互操作的工作原理

  正常運行(It Just Works)

  在Visual Studio NET C++ 互操作技術被稱為IJW或正常運行(It Just Works)在即將推出的版本中這被改為一個更具描述性的名稱互操作技術那麼它是如何正常運行的呢?對於每個由應用程序使用的本機方法而言編譯器同時創建了一個托管的入口點和一個非托管的入口點它們中的一個是實際的方法實現而另外一個是轉發轉化代碼它創建適當的轉換並進行任何必要的封送處理托管入口點幾乎總是實際的方法實現唯一的例外是該方法的代碼無法用中間代碼表示或者開發人員使用#pragma unmanaged編譯器指令來強制要求將入口點實現為本機代碼

  當使用一個IJW轉發轉化代碼時(例如當本機入口點是轉發轉化代碼時)編譯器提供轉化代碼的實現並通過一個偏移量或導入地址表(Import Address TableIAT)跳轉來調入實際的實現IJW轉化代碼處理的合理時間大約在個周期之間不過精心設計的測試用例可以使這個數字減至那麼小當轉發的轉化代碼是中間代碼時托管的P/Invoke就會派上用場P/Invoke僅包含一個聲明而沒有實際的方法實現CLR提供了對轉化代碼的運行時支持的功能這些轉發的轉化代碼通常都會比同等配置的本地機器實現稍微慢一點點

  如上所述使用IJW使每個函數都有兩個入口點一個托管的接口和一個非托管的接口但某些構造需要這些入口點的調用地點在編譯時進行填充(例如函數指針和vtable)而如果編譯器在編譯時無法知道運行時調用地點的托管狀態則它應該選擇哪一個入口點呢?在Visual Studio NET 編譯器總是會選擇非托管入口點當然如果調用方確實是托管的則上述做法就會造成一些麻煩這稱為Double P/Invoke問題在這種情形下托管調用對非托管轉化代碼進行的轉換剛好又轉換回托管代碼這樣的操作會導致幾個大的不必要的開銷

  Visual C++ 提出了幾個解決方案第一個方案就是使用__clrcall關鍵字通過這個關鍵字可以指定是否基於每個方法發出非托管入口點使用這個關鍵字添加函數聲明可以防止生成非托管入口點(這樣做的一個缺點就是該函數就不能被本機代碼直接調用)__clrcall關鍵字也可以放置在函數指針上這樣在編譯器有所選擇的情況下可以使用托管入口點來填充該指針Visual C++ 提供的第二個解決方案是通過運行庫檢查來自動消除Double P /Invoke問題而cookie將幫助運行庫確定是否可以跳過非托管的轉化程序從而將調用直接轉發至托管入口點不過這一功能不可能最終解決問題

  第三個解決方案是純中間代碼新的/clrpure編譯器選項指示編譯器生成一個不包含本機構造的純托管映像這樣不僅可以產生遵循CLI的程序集來支持部分信任的情況而且通過防止生成非托管的轉化代碼解決了Double P/Invoke問題結果是每個函數只有一個入口點(托管入口點)這樣虛表(vtable)和函數指針就決不會使用非托管入口點進行填充了

  然而僅僅因為代碼是遵循CLI的並不意味著它就是可驗證的而這對於支持低信任級別的情況(例如當從文件共享加載代碼時)是一個重要的目標所以Microsoft引入了一個更為嚴格的編譯器選項稱為/clrsafe對於C++開發人員來說這是可驗證性的聖杯使用這個開關會使編譯器確保生成的程序集是完全可驗證的任何無法驗證的結構都會產生編譯時錯誤例如試圖將一個整型指針編譯成一個變量將會產生這樣的錯誤int* = this type is not verifiable並指出包含非法結構的行在一些情況下走向這個極端是適當的例如將要作為SQL Server 中的存儲過程運行的所有托管C++代碼都應該使用此選項進行編譯

  數據及代碼的托管與非托管環境中不包含任何/clr選項將導致生成完全的本機映像使用/clr選項將會產生可以包含托管與非托管代碼和數據的混合映像通過使用/clrpure選項生成的純中間代碼不會包含任何非托管代碼盡管這仍不能保證是可驗證的而且可以包含本機類型安全中間代碼是可驗證性的最終目標(只針對NET框架而言)簡而言之這兩種新的編譯模式都將使以前不可能實現或者難以實現的多種情況變為現實

  優化

  所有優秀的軟件開發人員都想要確保他們的軟件能夠正常運行編譯器編寫人員是一種特殊的開發人員它們的代碼不僅需要正常運行而且他們的代碼生成的代碼必須盡可能地具有高的效率由於這個原因任何成功的編譯器的背後都要有一個好的優化支持在這方面Visual C++ 是無可挑剔的

  由於Visual Studio NET 和Visual Studio NET 在本機代碼的性能提高方面做了許多的工作所以它們加入了對C++編譯器的一些驚人的優化在加入了SSE和SSE體系結構的同時它們還提供了針對Intel Pentium IV的支持最為顯著的是加入了全局程序優化(Whole Program OptimizationWPO)它允許鏈接器在將每個經過編譯的cpp文件變成obj文件時對整個程序進行優化這些對象文件和普通的對象文件有所不同因為它們包含的不是本機代碼而是用來在編譯器前端和後端進行通信的中間語言然後鏈接器就能將所有這些文件優化成一個大的單元從而提供更多的內聯機會更好的堆棧對齊方式和在各種情況下使用自定義調用約定的可能性Visual C++ 使用稱為自頂向下自底向上分析這樣的新功能來改進(WPO)但是大的改進是以特性引導最優化(Profile Guided OptimizationPOGO)的形式出現的在編譯器中提供的這種全新的功能將會對性能有所改進

  就編譯器而言對源代碼的靜態分析將會留下許多懸而未決的問題如果在一個if語句中比較兩個變量第一個變量比第二個變量大的幾率是多少?在一個switch語句中哪一個case被選中的次數最多?哪些函數最常使用而哪些代碼常常被忽略?如果編譯器在編譯時知道代碼在運行時應該如何使用的話它就可以為大多數情況進行優化這正是Visual C++ 編譯器所能夠做到的

  POGO第一步需要編譯代碼並將其鏈接成一個規范構建它具備一組分析探測器當使用WPO時由編譯器生成並導入到鏈接器的對象文件是由中間語言而不是本機代碼構成的這些探測器分為兩種值探測器和計數探測器值探測器用來構造變量存放的值的直方圖而計數探測器用來跟蹤您通過該應用程序往返某一特定路徑的次數當應用程序運行並正常使用時數據是從所有這些探測器中集合匯集而成的並被寫入一個配置文件數據庫這些配置文件數據和原始的obj文件一起被導入到鏈接器鏈接器能夠分析配置文件數據確定應該應用的其他優化並生成一個新的非規范應用程序構建這只是一個經過編譯的版本而不是一個可以用來發布給客戶的規范版本

  特性引導最優化(POGO)支持各種各樣的優化以計數探測器為基礎可以在每個函數調用地點做出內聯決策通過使用值探測器可以重新排列switch和ifelse構造這樣就可以提取出最常用的值並且避免在找到常見的case之前進行額外的不必要檢查還可以重新排列代碼段從而能夠將最常用的路徑排在一起而不是強制要求在代碼內進行不必要的跳轉這避免了開銷很高的轉換檢測緩沖器(Translation Lookaside BufferTLB)顛簸和頁面調度(paging)

  可以將不常使用的代碼放在該模塊的一個特定的部分中這也有助於避免上述問題可以執行虛擬調用的途徑因為虛擬調用地點常常導致對某一特定的類型進行調用從而能夠避免常見情況中的虛表查找可以執行局部內聯借此確保只對函數中經常使用的代碼段進行內聯而且這種決策是基於每個調用地點做出的此外某些代碼段會以某種優化為目的進行編譯而其他代碼段的編譯則有著不同的目標例如經常使用的和/或小的函數可以編譯為最大化速度(/O而不經常使用和/或大一些的函數會被編譯為最小化空間(/O

  如果能夠了解實際情況並且能夠將應用程序投入經常使用的情況而與此同時還進行一些規范化的工作則應用程序的性能將會得到極大的提高最近使用POGO對SQL Server進行了重新編譯並且在許多常見的情況下獲得了%的性能飙升這樣下去可以斷定Microsoft會開始使用這一技術來將它的許多產品進行編譯需要注意的是在分析規范構建時不要試圖涵蓋全部的代碼這一點非常重要POGO的全部意義在於確定如何優化常見的用例如果試圖涵蓋全部的代碼將得到深刻的教訓

  Visual C++ 還增加了對OpenMP的支持它是一個構建多線程程序的開放規范它由一組程序組成用來指示編譯器代碼的哪些部分可以平行放置如果代碼具有大的循環並且這種循環與前面的迭代沒有依賴關系則這種代碼最適合使用OpenMP看一看下面這個簡單的copy函數它將數組a和b中的值相加並將結果存儲在c中

  void copy(int a[] int b[] int c[] int length)
{
    #pragma omp parallel
    for(int i=; i<length; i++)
    {
        c[i] = a[i] + b[i];
    }
}

  在擁有多個處理器的機器上編譯器會生成多線程來執行這個循環的迭代而每個線程將執行復制操作的一部分需要注意的是編譯器無法驗證循環是否具有依賴性因此它不會阻止在不適當的情況下使用這些代碼如果具有依賴性就極有可能得到與想像的不同的錯誤結果盡管它們在規范方面是正確的

  雖然使用OpenMP的最大好處常常來源於平行放置循環(如剛才的例子所示)但是在直線型代碼中使用它也會使性能得到改善#pragma omp section指令可以用來區分一段代碼中的非依賴性部分這樣就可以讓開發人員指定可以並行運行的區域然後編譯器就可以產生多線程從而在不同的處理器上執行這些代碼段

  對於使用NET的開發人員來說一個重要的改變是Visual C++ 優化器對中間代碼做出的優化和對本機平台做出的優化大體上是一樣的盡管優化器是通過不同的調優來做到這一點的而現在的JIT編譯器是在運行時分析並優化的它允許C++編譯器在初次編譯時就進行優化這樣也可以提供極大的性能優勢(C++編譯器就可以有更多的時間來進行分析而不是保持JIT)Visual C++ 編譯器首次對托管類型進行了優化執行循環優化表達式優化和內聯但是在有些地方編譯器是無法對基於NET的代碼進行優化的例如由於指針算術運算的無法驗證性它就有一些無能為力了而且某些代碼由於CLR對類型和成員可訪問性的嚴格要求而無法內聯盡管編譯器的確對合法的內聯機會進行了大量的分析此外優化中間代碼需要平衡考慮對JIT編譯器的影響例如不可能去解開一個循環而暴露過多的變量給JIT編譯器因此JIT編譯器必須進行注冊表分配(一個NP完成問題)Visual C++小組正在對這些問題進行研究並在系統發布時會得到一個經過良好調優的優化解決方案

  安全性

  在Bill Gates發出了可信任計算(Trustworthy Computing)倡議這對Microsoft開發的所有產品都有著不可小視的沖擊力Windows操作系統的開發人員在安全性培訓和代碼評審上花費了幾個月的時間這使得Windows Server 成為該公司曾經發布過的安全性最高的一個操作系統Microsoft Office 還包含了許多安全功能例如Information Rights Management (IRM)更好的宏安全性和在Outlook中阻止HTML下載等等而編譯器小組也在大踏步地使他們開發的編譯器及其生成的代碼變得更安全Visual Studio NET 引入了緩沖區安全檢查/GS編譯器選項這一標志將導致編譯器在決定為有緩沖區溢出攻擊嫌疑的函數返回地址之前就預先在堆棧上分配空間在函數進入時一個帶有已知計算值的安全Cookie會被放在這個緩沖區中而在函數退出時編譯器會對其進行檢查以確保該Cookie沒有被破壞對Cookie值的更改意味著有重寫返回地址的可能性而這會產生一個錯誤並導致應用程序終止當然這並不能防止所有的緩沖區溢出攻擊Visual Studio NET 還增強了/GS功能它通過對堆棧上的局部變量進行排序來將數組分配在高於其余局部變量的內存地址上從而防止這些局部變量造成溢出這樣會阻止基於vtable入侵的攻擊和其他基於指針的攻擊

  Visual C++ 對這一強大的功能進行了又一次升級當進行函數調用時函數的激活記錄按照如圖所示的方式放置如果一個局部緩沖區發生了溢出黑客就有可能重寫其堆棧上的所有內容其中包括異常處理函數記錄安全cookie幀指針返回地址以及函數的參數這些值中的大多數都是通過各種機制保護的(例如安全異常處理)然而在一個以函數指針作為參數的函數中利用緩沖區溢出仍有可能如果一個函數將函數指針(或者一個包含函數指針的結構或類)作為參數黑客就可能會重寫這一指針的值並使代碼執行任何他想要運行的函數為了防止這一點Visual C++ 編譯器會分析所有的函數參數來防止這一漏洞那些易受攻擊的參數會在堆棧的局部變量下創建副本然後使用這些副本而不是參數本身緩沖區溢出可能會重寫原始值但是它們不會被利用因為正在使用的副本仍然保持原始值

  與缺省安全(secure by default)可信任計算指令相一致的是Visual C++ 編譯器現在默認支持緩沖區安全檢查選項這會有助於使所有用Visual C++編譯的產品更加安全實際上Microsoft現在正在用這個已經啟用的選項來構建它的所有產品其中包括WindowsOffice和SQL ServerVisual C++ 在其他方面的進步就是確保代碼是以考慮安全性為前提開發的絕大多數C++應用程序都依賴於C運行時庫(CRT)和標准模板庫(STL)這些庫在最初設計時代碼安全性不具有高的優先級而許多現在普遍使用的攻擊也不存在這樣做的結果是許多由這些庫提供的功能常常以一種不安全的方式被使用從而使得應用程序暴露在一些潛在的攻擊之下最近出版的書籍(如Michael Howard撰寫的Writing Secure Code(Microsoft Press年)中都強調了某些編碼實踐的重要性這些實踐都不以這些庫為例在Visual C++ Microsoft推出了經過重新編寫的這些庫的新版本它致力於找出所有可能導致一般安全問題的函數並提供一些更安全的替換版本這項工作的長遠目標是棄用所有不安全的版本而提倡使用一些具有相同功能但更加健壯的版本單單是CRT的這個新版本就引入了超過個新的安全函數從而可以確保檢查所有的指針參數是否為空值並且所有進行內存復制操作的函數除了知道目的緩沖區和源緩沖區之外還要知道需要復制多少字節的數據

  小結

  Visual C++ 的新功能還有很多難以在此一一詳述混合映像的延遲CLR加載本機應用程序域 API為在應用程序域和進程方面提供更好的全局變量支持而引入的新的聲明規范模塊構造函數為對象文件和NET模塊提供的鏈接器支持隱式裝箱使用與C#開發人員所喜愛的語法一樣的XML注釋全新的面向NET框架的STL版本參數數組別名提示新的浮點模型運算符重載等等

  任何基於NET框架的語言的新版本經常讓人們想問如果我們的開發小組想要編寫一個面向NET的應用程序應該使用哪一種語言?現在如果您正在做許多本機互操作工作那麼很簡單C++是開發本機互操作最易於使用的語言而且它常常具有最佳的性能此外如果您想要將現有的C++應用程序轉到NET上那就的確沒有更好的選擇了實際上當您把現有的應用程序轉化為NET框架時使用Visual C++是Microsoft極力推薦的一條途徑

  至於新的應用程序您或許會問為什麼不熟悉NET的開發人員選擇一種語言而不選擇另一種語言由於每種語言都有其優勢所以無法對這個問題作出簡單的回答但對於純基於NET的應用程序而言C#Visual Basic和C++中的體驗基本相同如果您作為一個開發人員已經習慣於使用某種特定的語言就沒有什麼重要的原因要轉向使用另外一種語言了

  但如果您正在開發某種互操作您也許會選擇C++語言而不是其他使用C++體驗肯定要好於其他的語言因為在C++中直接內置了范圍廣泛的互操作支持此外它通過析構函數提供的確定性清除功能在消除資源洩漏和確保應用程序的正確性時簡直就是無價之寶C++還有許多強大的功能可以與CLR提供的功能聯合使用例如C++不僅支持模板和泛型而且還支持它們的組合這比單獨使用其中任何一個功能更富有表現力也更為強大尤其有用的一個庫編寫技術是編寫實現一般接口的模板這會為您的模板提供所有的靈活性和強大的功能(例如專用化)而它仍會讓其他語言有通過一般接口直接使用從模板實例化的對象的能力總而言之C++的確找到了屬於自己的位置


From:http://tw.wingwit.com/Article/program/ASP/201311/21791.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.