C 語言會比 C++ 快?

2020-12-17 CSDN

和面向過程的 C 語言相比,其繼承者 C++ 不僅可以進行 C 語言的過程化程序設計,還可以進行以繼承和多態為特點的面向對象的程序設計。要論兩者上手的難易度,對此,有網友評價道,學好 C 只要 1 年,而學好 C++ 需要的可能不止 10 年。然而這麼多年過去了,C++ 卻一直未能取代 C,且在本文中,作者發現自己常用的庫,可以使用到的 C++ 特性越來越少,進而準備向 C 語言過渡。

作者 | Arseny Kapoulkine

譯者 | 姜松浩

責編 | 屠敏

以下為譯文:

我最近經常使用 meshoptimizer(https://github.com/zeux/meshoptimizer) 這個庫,隨著時間的推移,能用到的 C++ 庫的特性越來越少。截至目前,儘管其中仍包含一些 C ++ 特性,但整體上來看,其代碼已與 C 極為相似。

這些變化背後有很多原因,例如刪除 C++ 11 的要求可以確保任何人都能在任何平臺上編譯庫;刪除 std::vector 大大改進了未優化構建的性能;刪除 algorithm 可以提升編譯速度等等。但是,我目前更改的這個代碼庫並沒有完全變成 C 語言的代碼。今天我們來探索這個特定算法,網格簡化器,後面簡稱為 simplifier.cpp,看看這個算法的全部 C ++ 實現的範圍,是否值得一直改進到 C 語言的版本。

方法

這個網格簡化器,是通過多次調整以改善代碼的性能和質量的一種基於邊緣壓縮的二次曲面簡化算法的實現結果。該算法仍處於開發階段,但已投入相當大的努力。細節真的不那麼重要,但它有助於理解結構和大小:

整個算法在一個獨立的 .cpp 文件中實現,該文件幾乎有一千行代碼(撰寫本文時為 1004 行),包括注釋,空行,帶括號的行等。該算法基本上只使用堆分配的數組作為數據結構,並使用原始指針。該算法需要一個自定義實現的哈希表和一個排序例程。我們將看一下實現的幾個變化過程,首先是從使用 C ++ 容器和算法的變化開始,這將有助於該算法,然後一次刪除一個 C ++ 特性並測試編譯速度和運行時的性能。我們使用了三種編譯器,分別是 gcc7.3、clang 6 和 msvc 2017,並將它們運行在 Core i7-8700K 上的 Windows 10 / Ubuntu 16.10 系統中。我們將通過編譯一個 .cpp 文件(debug 使用默認選項,release 使用 -O2)來測量編譯性能,並通過將 buddha.obj (1M 左右的三角形網格)簡化為其大小的 25%,用來測試運行時性能。在我們達到這一狀態之後,我們再來探究將代碼更改為純 C99。

請注意,我完成這些實現的方式是通過獲取現在可以在存儲中看到的代碼,並將其更改為更慣用的 Modern C ++ [1]。但是,這些通常與之前版本的simplifier.cpp非常接近,不同之處在於現在可以直接比較變化。

Baseline:很多 C ++ 代碼

我們開始的版本是來自 current meshoptimizer master 的原始 simplifier.cpp,具有以下修改:

所有原始指針都更改為 std::vector我們使用 std::unordered_set 取代原自定義的哈希表我們使用 std::sort 取代原自定義的排序例程下表是我們得到的結果:

compiler/stl

debug compile

release compile

debug run

release run

gcc

520 ms

646 ms

2273 ms

572 ms

clang

400 ms

684 ms

2356 ms

566 ms

clang libc++

400 ms

725 ms

1535 ms

584 ms

msvc

422 ms

566 ms

36317 ms

579 ms

從表中我們可以看出來這是一個很好的開始。我們可以看到性能在發布運行時非常的穩定,簡化 1M 三角形網格用了 0.6 秒是一個很好的性能水平。通常在調試時或多或少基本都是合理的,除了一個明顯的例外 MSVC(MSVC STL在調試模式下的不良行為是一個強制函數會將從 meshoptimizer 中刪除所有 STL 的使用)。而在編譯時長方面基本都有所不同,但沒有特別奇怪的情況。

為了正確看待編譯時長,Jonathan Blow 最近發布了一個帶有編譯器性能改進的視頻流(video stream with compiler performance improvements),他的遊戲引擎和用他的新語言編寫的遊戲在大約一秒內完成編譯和連結(編譯本身大約需要 0.9 秒)。這是在具有 100K 行的代碼庫上, 而我們的算法只有 1K 行代碼(當然我們的代碼中不包括 STL 雖然排除 STL 並不完全公平,但是加入 STL 計算代碼行也不完全公平,因為我們知道我們的算法完全可以在沒有任何 STL 依賴的情況下用 1K 行代碼來實現)。在編譯代碼時你會注意到 400 毫秒,即使它只有一個文件。而當我處理代碼的時候,這樣的事情會讓我不那麼開心,因為有很多這樣的文件,累積的編譯性能可能會很差。這樣的情況是因為我們的實現對於 STL 依賴非常簡單,我們只使用其中三個算法/容器。讓我們看看當我們停止使用其中一個時會發生什麼。

首先不使用 unordered_set

我們基準測試的先前版本的秘密就在於 unordered_set 從來沒有在那個版本中存在過。雖然 meshoptimizer 最初使用的是 STL 容器和算法,但它從未使用過 std::unordered_set。因為根據以前的經驗,我預計性能不足以滿足我想要編寫的算法類型,但是有一個自定義替代方式就是使用二次探測在一個大的二維數組中實現,這類似於谷歌的 dense_hash_set 設計。它是我通常在不同的代碼庫中為不同的應用程式經常實現和使用的一種哈希表,所以我對它非常熟悉。在 simplifier.cpp 中的實現只有35行代碼 [2],可見這個方式很容易插入並適應手頭的用例。讓我們看看當我們使用它時會發生什麼。

compiler/stl

debug compile

release compile

debug run

release run

gcc

334 ms

461 ms

2054 ms

460 ms

clang

270 ms

517 ms

2152 ms

452 ms

clang libc++

314 ms

609 ms

1179 ms

415 ms

msvc

361 ms

461 ms

28337 ms

380 ms

從結果上看,額外的 35 行用於手動實現的更好的哈希表是值得的。我們在整個版本,調試/發布以及編譯時長和運行時長方面都看到了顯著的性能提升。運行時長性能的最大提升是在 MSVC 編譯器上,我們快了 1.5 倍,事實上哈希表沒有被用作算法的核心部分,它僅僅是用於在算法開始之前建立各個頂點之間的唯一性關係。

測試結果突顯了 std::unordered_set 不適用於起決定性能的重要工作,特別是那些插入量很大的工作負載。不幸的是,這不是一個實現缺陷,因此無法糾正,這個問題是無序容器的標準要求妨礙了更有效的實現。希望最後我們能夠在標準中得到一個更好的哈希表。

高估了精確的排序算法

在開發 simplifier 的過程中,對各種網格的重複分析表明,大量時間都花在了 std::sort 上。現在,std::sort 不是最快的排序算法,但它通常與自定義實現相比極具競爭力,並且在不改變問題的情況下很難被擊敗。在我的例子中,排序用於邊壓縮的數組,排序鍵是一個浮點錯誤值,所以很自然的是使用3遍基數排序,依次使用鍵的11位,11位和10位(浮點值為32位每次使用一部分用於排序)。但是,我們這裡有一個有趣的替代方案,我們可以使用11位的排序鍵僅需進行1次基數排序 [3]。

我們有一個 32 位的非負浮點值會發生什麼;如果我們取前 12 位並忽略最前面的1位(因為第一位是一個符號位且始終為0),我們得到11位代表8位指數和3位尾數,這本質上給了我們一個近似的數值但卻存在一個嚴重的捨入錯誤。如果我們使用此值作為鍵進行排序,那麼排序順序就不會完全按照完整的32位鍵進行排序。然而,在我們的例子中,我們需要排序以便能夠首先更好的基於啟發式算法處理邊壓縮,並且啟發式算法是一個粗略的近似過程,因此我們的排序帶來的額外錯誤並不明顯。這種技術在其他您不一定需要確切順序的領域中非常有用。一次基數排序的好處是它更快(你只需要對數據進行1次排序而不是 3 次!)並且比完整的基數排序更容易實現,只需 36 行代碼 [4]。

compiler/stl

debug compile

release compile

debug run

release run

gcc

287 ms

403 ms

949 ms

334 ms

clang

230 ms

461 ms

962 ms

327 ms

clang libc++

312 ms

546 ms

940 ms

328 ms

msvc

330 ms

430 ms

26824 ms

285 ms

這次編譯時間的增加稍微適度。我們已經刪除了<algorithm> 標題,但它似乎並沒有對編譯時長產生非常顯著的好處,因為我們仍然存在 <vector>,並且可能兩者都提取了一些大 STL 頭文件。但是,對運行性能的影響非常顯著,特別是在 libstdc ++ 中的調試運行性能上(很可能 std::sort在調試中非常慢)此外在發布版本的收效上也同樣令人興奮。從這張圖表的結果中觀察不出來排序算法變得多麼的快,與其他工作相比,它幾乎完全從配置文件中消失,然而整個算法「僅」運行速度快了 1.35 倍。但單純在排序代碼上測試得到的收效更好,在發布版本中從 117 毫秒減少到了 10 毫秒。

再見,std :: vector

有一個數字是我們尚未大幅度調整的,那就是使用 MSVC 在調試代碼所需的時間。雖然很自然的會想到未經優化的構建過程會比優化後的慢,但是優化後的代碼它們必須足夠快才行。有時你希望在有意義的輸入數據集上調試問題。有時你希望運行調試能夠進行全面檢查以通過你的測試,確保在發布版本中它們不會觸發任何可能在隱匿的 bug。有時你試圖調試程序的不同部分,但仍需要運行其餘部分。程式設計師創造性地提出了許多變通方法,使問題不那麼嚴重,例如你可以製作特殊的構建來實現一些優化而不是全部,你也可以對不同的項目使用混合優化設置,你可以使用 #pragma optimize 這樣的指令來暫時禁用有問題的部分的代碼的優化,但所有的這些看起來像是臨時使用的補丁。讓我們嘗試用一個非常簡單的動態數組替換我們仍然使用的唯一 STL 組件 std::vector。我們的代碼中不需要 resize 或 push_back,所有數組都使用正確的大小進行初始化。我們的要求足夠低,我們這種 std::vector 的替代方式僅僅需要 40行代碼 [5],並且主要由 operator[] 定義組成。

compiler/stl

debug compile

release compile

debug run

release run

gcc

158 ms

303 ms

980 ms

318 ms

clang

138 ms

320 ms

1021 ms

297 ms

clang libc++

142 ms

324 ms

1028 ms

299 ms

msvc

156 ms

219 ms

3482 ms

265 ms

上表中的結果相當的有趣。通過用我們自己的類型替換 std::vector ,我們不僅顯著提高了 MSVC 的調試性能,而且還減半了我們測試使用的幾個編譯器的編譯時間。gcc / clang 中的調試運行性能有點退步,我相信這是因為我的替換代碼中使用 assert 來對每個 operator[] 訪問執行邊界檢查,而在 libc ++ 和 libstdc ++ 中,它們分別使用 _GLIBCXX_ASSERTIONS 和_LIBCPP_DEBUG 單獨定義來完成邊界檢查控制的。為 std::vector 的變量啟用這些定義會將兩個庫的調試運行時長提高到大約 1350 ms [6],因此在啟用類似功能時,我們的替換代碼運行速度會更快。

發布的性能整體來看也略有提高,這是因為對於我們代碼中的許多數組而言,std::vector 的構造函數執行的默認初始化是多餘的,因為我們無論如何都要填充數組。當然,使用 std::vector,你也可以 resize 那些大數組的大小,然後計算條目(這需要對每個條目進行冗餘的默認初始化),或者 reserve 和 push_back(這需要更多的代碼來每個條目進行添加,而這個花銷是累加起來的)。與之相反的是,使用自定義容器,可以輕鬆地選擇跳過初始化。實際上,在我們的替換代碼中這是唯一的選項,因為如果需要,可以很輕鬆的手動添加 memset 設置數組大小。

又回到了之前

帶有邊界檢查的 operator[] 的自定義容器大部分都是成功的,但它並不能讓我滿意。在某些算法中,容器的額外成本仍然非常龐大。在一些算法中,內部函數將使用原始指針來最佳化發布的運行性能,這意味著無論如何都不會執行邊界檢查。此外,算法輸入使用原始指針,需要仔細處理。由於在許多關鍵位置使用了原始指針,我會使用 Address Sanitizer 作為 CI 管道的一部分運行構建,偶爾也會在本地運行嘗試,因此我對缺少越界訪問感到安全。在沒有自定義可視化工具的情況下,調試器將無法顯示數組,更關鍵的是,在評估成員訪問權限時會遇到問題(這在 std::vector 中也是如此,因為這取決於調試器),這使得查看表達式更加複雜,調試也不那麼愉快。現狀是既沒有提供完美的安全性,也沒有提供完美的性能,因此我決定嘗試使用原始指針。

當然,容器的另一個好處是對內存洩漏的額外保護,由於我不是特別熱衷於記住釋放每個分配的指針,所以我創建了一個 meshopt_Allocator 類[7]。這個類可以分配大塊的類型數據並且會記住分配的每一個指針,在運行末尾階段,它將會刪除所有已分配的塊。這導致融合的分配器+數組的類被拆分為兩部分,一個是特殊的分配器類用於完成了內存管理任務,而對於數組而言一個原始指針就足夠了。Address Sanitizer,以及嚴格的測試和手動填寫的斷言聲明,這些將保持代碼正確。

compiler/stl

debug compile

release compile

debug run

release run

gcc

147 ms

260 ms

720 ms

320 ms

clang

132 ms

294 ms

699 ms

301 ms

clang libc++

131 ms

297 ms

697 ms

300 ms

msvc

141 ms

194 ms

1080 ms

261 ms

雖然我對這種權衡並不是百分之百滿意,但到目前為止它仍然運行良好。刪除與確定每個函數中是否應該使用原始指針、迭代器或容器相關的檢測的開銷是很有必要的。值得注意的是,使用 Address Sanitizer 構建的開銷是非常合理的,並且使用它會讓我感覺更安全,因為它會捕獲容器中的問題邊界檢查的超集。

compiler/sanitizer

compile

run

gcc

147 ms

721 ms

gcc asan

200 ms

1229 ms

gcc asan ubsan

260 ms

1532 ms

clang

135 ms

695 ms

clang asan

154 ms

1266 ms

clang asan ubsan

180 ms

1992 ms

讓我們來試試改成 C

一旦我們切換到原始指針,我們的代碼中 C ++ 就剩不下多少了。偶爾還有一兩個模板(template),但實例化的數量足夠小,這樣我們可以僅為我們需要的每種類型複製代碼。meshoptimizer 使用了 C ++ 中的指針類型強制轉換和函數調用方式的強制轉換(例如int(v)),但 C 語言沒有這兩種強制轉化的方式,所以必須對代碼進行相應的調整。同樣,我們還遇到了一些其他的語法問題,但實際上在這一方面將代碼更改為 C 語言的版本並不難。這樣做的確需要更多的犧牲,還有就是 MSVC 的問題,要麼我們必須使用 C89,要麼將我們的 C99 代碼編譯為 C ++,除非我們願意只支持最新的 MSVC 版本,但這樣做確實是可行的。在我們停止使用每個 C ++ 標準頭之後,這些真的重要嗎?

compiler/stl

debug compile

release compile

debug run

release run

gcc

105 ms

209 ms

710 ms

321 ms

clang

95 ms

254 ms

711 ms

310 ms

msvc c++

139 ms

192 ms

1087 ms

262 ms

msvc c99

125 ms

180 ms

1085 ms

261 ms

對 gcc / clang 編譯時間有顯著影響,我們通過將代碼切換到 C 語言之後可以節省大約 40 ms。此時的真正區別在於標準頭文件上。例如simplifier.cpp 使用的 math.h 這個頭文件在 C ++模式下與 C 模式下相比實際上大了不少,一旦默認編譯模式設置為 C ++ 17 時,這種差異將會增加得更多:

compiler

c99

c++98

c++11

c++14

c++17

gcc

105 ms

143 ms

147 ms

147 ms

214 ms

clang

95 ms

129 ms

133 ms

134 ms

215 ms

clang libc++

95 ms

130 ms

132 ms

136 ms

140 ms

問題是 math.h 在 gcc / clang 編譯時會包含 cmath,cmath 會帶來很多 C ++ 機制進而增加運行成本,而在 libstdc ++ 中的 C ++ 17 中則會添加一連串新的特殊函數,這些函數卻很少有用,但無論如何都會使編譯速度變慢。在這種情況下,刪除對 math.h 的依賴很容易[8]:

#ifdef __GNUC__#define fabsf(x) __builtin_fabsf(x)#define sqrtf(x) __builtin_sqrtf(x)#else#include<math.h>#endif

就是上述這樣的方式一直到改成 C 語言版本的編譯時間。這絕對是 libstdc++ 和 libc++ 未來可以改進的領域。我認為對使用 C 的頭文件的而言,承擔 C++ 的包帶來的成本是不合理的。除了 math.h 問題之外,假設在編譯時間中 C 語言的代碼有意識的用到了 C++ 的子集,這樣的結果看起來 C 語言版本的代碼的編譯時間並不比 C++ 版本的快,所以這種情況的時候切換到 C 語言也不能保證 meshoptimizer 會更快。

結論

希望通過 simplifier.cpp 中的過去、現在和未來可能的變化進行探索是有用的。在製作 C / C ++ 庫時,重要的是要注意不僅僅只有代碼的正確性,而可移植性、編譯的簡易性、編譯時間、在調試和發布時的運行時間、可調試性等等,所有的這些都很重要,這些有助於減少庫和代碼貢獻者之間的衝突。C ++ 是一種無情的語言,但是,如果有足夠的時間和精力,就可以獲得良好的表現。前提是你願意質疑一切,甚至包括有時被認為是極其常見的方法,例如 STL 或 CRT 的有效性或效率。

我們在調試模式下在 gcc 中用了半秒的編譯時間,在 MSVC 中用了 36s 的運行時間,並以 gcc 的 100ms 編譯時間和 MSVC 上大約一秒的運行時間結束,這種方式使用起來更加愉快。當然,在 1K 行編譯 100ms 的前提下,並假設是線性關係,每 10K 行我們大約需要一整秒,這樣的結果仍然比其他一些語言慢得多,但這對於在單核上運行的完整構建來說並非完全不合理。為開發多年的大型代碼庫提供服務是一個更難的問題,這些將留給讀者作為練習了;)

點擊此連結(https://gist.github.com/zeux/bf847986e0474cf48f61bb5749da38e4)可以獲得對 simplifier.cpp 的所有源修改;按照文章中描述的順序,它們依次是simplifiervsm.cpp、simplifiervs.cpp、simplifierv.cpp、simplifierb.cpp、simplifier.cpp、simplifier.c。

註:

[1]:去年,在工作中討論了 C ++,有人說「這是一個很好的 C++ 子集,一個擁有類的 C 語言」,我回答說「有一個更好的子集,擁有結構的 C 語言」。這就是大多數 meshoptimizer 原始碼的樣子,除了幾個模板。

[2]:哈希表接口只有兩個函數,hashBuckets和hashLookup:simplifier.cpp:124 。

[3]:使用 11 位是一個合理的選擇,因為它需要一個 2048 條目的直方圖,它需要 8 KB 並且可以輕鬆地適應 16 KB 的 L1 緩存。給定 32 KB L1 高速緩存,你可以將直方圖擴展到 12 位,但超出此範圍通常效率較低。你可以在 Pierre Terdiman 的 Radix Sort Revisited 文章中閱讀有關基數排序的更多信息(http://www.codercorner.com/RadixSortRevisited.htm)。

[4]:sortEdgeCollapses 函數的完整實現可在此處獲得: simplifier.cpp:712(https://github.com/zeux/meshoptimizer/blob/c93ba0987baa84bd73b61edf1c0ba7ba2e48df4b/src/simplifier.cpp#L712)。

[5]:這個類不再是 meshoptimizer 的一部分了,但你可以在這裡查看較舊的稍長版本:meshoptimizer.h:605(https://github.com/zeux/meshoptimizer/blob/5b0d10bb3c0c174965b716dda3270bce4f3278b6/src/meshoptimizer.h#L605)。

[6]:我在調查了調試中的奇怪性能差異後發現了這一點;我不願重複所有先前測試用例的調試基準,所以我假設開銷是 std::vector 導致的額外的大約 30%。希望這不會改變一般情況。我不確定為什麼默認情況下這些斷言沒有首先啟用,這似乎不是用戶友好的,但這應該反映了使用這些庫的默認體驗。

[7]:所有 meshoptimizer 中的算法都使用了此類,可在此處獲得: meshoptimizer.h:662(https://github.com/zeux/meshoptimizer/blob/c93ba0987baa84bd73b61edf1c0ba7ba2e48df4b/src/meshoptimizer.h#L662)。

[8]:這感覺就像補丁,而且我必須獨立地應用於多個源文件,所以現在我選擇不這樣做。但是,如果在修復此問題之前 C ++ 17 模式成為默認模式,我將不得不重新考慮,因為 2x 編譯時間損失有點太大了。

原文:https://zeuxcg.org/2019/01/17/is-c-fast本文為 CSDN 翻譯,如需轉載,請註明來源出處。

相關焦點

  • C語言,C++,C ,Java之間的關係
    C語言,C++,C#,Java,這幾種語言,應該說是當前最流行,也是最基礎的計算機語言。是不是有些人看著會頭大,大腦會不叫混亂,一個計算機怎麼會有那麼的的語言呢?看著就頭大。現在,隨著計算機的性能越來越強大,其內部的機構也會變的越來越複雜,很多新的東西也正在漸漸的出現並且發展起來比如大數據,vr,等。現在,小編先來給大家說下計算機語言的發張,一臺計算機最本質的語言是機器語言,由01010101的代碼組成,CPU處理的也是由由010101的代碼組成的數據。但是,這種語言太簡單了,不好理解。
  • python+C、C++混合編程的應用
    排序說明不了語言的好壞,反應的不過是某個軟體開發領域的熱門程度。語言的發展不是越來越common,而是越來越專注領域。有的語言專注於簡單高效,比如python,內建的list,dict結構比c/c++易用太多,但同樣為了安全、易用,語言也犧牲了部分性能。
  • C語言與C++混合開發?命中會遇見的extern "C"!
    回顧下之前的內容,我們的源碼,會經歷預處理,編譯,彙編,連結,而C與C++最終相遇,要解決的問題,會在連結過程遇到,我們來看下具體代碼:add.cgcc -o add.o -c add.cgcc -o hello.o -c hello.cppgcc add.o hello.o -o hello連結出錯,我們看下出錯信息:
  • 九大程式語言優缺點第四期:c++
    上一期給大家介紹了C語言,理所應當的本期給大家了解下C語言的超集,c++那麼接下來給大家介紹主流程式語言:C++、JavaScript、C#、Ruby、PHP以及Objective-C,對於這幾種語言,大家都能看到由其打造的頂尖應用,我們一起來了解一下吧。
  • C/C++優勢究竟在哪裡?是什麼讓他們經久不衰?看看這個你就懂了
    c語言誕生於1972年,是一個通用型命令式計算機程式語言,其支持結構化編程,詞彙變量範圍與遞歸,同時也是一種能夠預防各類未預期操作的靜態類型系統,最初的目標在於構建編寫系統軟體。相較於C語言,c++誕生於1983年,緊隨c語言的步伐,c++是C語言的超集,大家所知道的C語言是面向過程的,java是面向對象的,那麼C語言為了面向對象,所以誕生出現在大家所熟知的c++,被廣泛視為大規模應用構建軟體。
  • C+相比其他語言到底難在哪裡?
    看過程式語言排行榜的都知道,c/c++自02年以來,不管時代如何發展,其排名一直在前五以內,足見其在程式語言界的地位。編程界流行這麼一句話:c幾乎什麼都能做,c++幾乎什麼都能做好,足見其功能的強大。
  • C語言?c+?到底先學哪個才能更好的理解編程,這些你造嗎
    本身C語言和c++的編程方法不同,一個面向過程,一個面向對象。而要做出大型的、複雜的、精彩的程序,面向對象的語言就更適合。所以要學習c++這樣的語言。但是,1.c語言是好多學校的基礎課;2.c語言很容易描述算法;3.軟體開發過程中也有很多面向過程的開發,以及模塊化程序設計思想。要學習這些,比起學c++的複雜、困難程度,學c語言就可以達到上述目的。
  • C語言編程工具選擇
    常用的編程工具主要有VS, code::blocks,devc++等,devc++安裝簡單、佔用空間少、程序調試方便,完全能滿足信息學競賽編寫程序的要求,下面介紹devc++的安裝和使用。2、設置運行devc++,初次運行需要進行設置,語言一般選擇簡體中文,如圖八。
  • 剖析C語言中a=a+++a的無聊問題
    這種純屬C語言 「二」 級的問題應該是從a+++a引申出來的吧。於是乎兄弟姐妹們開始討論它的運算結果,以及改如何理解。更有人寫出(a++)+(++a) a+(++(++a)) ((a++)++)+a這樣的東西,問應該如何計算。我表示鴨梨很大...
  • Linux下C語言編譯的問題
    我們依然回到第3小節所討論的問題中,在最後,如果我們把連結的庫的順序換一下,看看會發生什麼結果?  gcc -o main main.o func.a test.a  我們會得到如下報錯.在c++代碼中連結c語言的庫  如果你的庫文件由c代碼生成的,則在c++代碼中連結庫中的函數時,也會碰到undefined reference的問題。下面舉例說明。
  • C 2 C++進階篇(1)
    首先談談筆者的水平,只學過c和數據結構,接觸過指針,對於取地址&從來沒有接觸過(因為據說是老師說不符合嚴謹的c....), python
  • 兩道騰訊 C/C++ 面試題及答案
    星標公眾號免費獲取C語言學習路線
  • 編譯器 | 五款好用的C/C++編譯器(IDE利器)
    應一些看官老爺的後臺留言需要說一些編譯器和應對的環境,這篇文章屬於編譯器專題,這次我們討論的是的語言: C 和 C++,它們有著許多卓越的特性。後面會帶來不同種類語言的不同使用環境中所使用的編譯器 or IDE。前三個集成 IDE +個 VS 是一些工作中常使用的,後面兩個是一些使用到 C/C++語言的比賽中經常使用的。
  • c++的輸入與輸出
    c++輸入與輸出C++ 標準庫提供了一組豐富的輸入/輸出功能,本章將討論 C++ 編程中最基本和最常見的 I/O 操作。輸入輸出並不是c++語言的正式組成成分,c和c++沒有為輸入輸出提供專門的結構。在c語言中輸入輸出是通過調用scanf和printf 實現的,在c++中是通過調用流對象cin和cout實現的。
  • 《信息奧賽一本通》第一章C++語言入門課後題解
    的程序,這個程序常常作為一個初學者接觸一門新的程式語言所寫的第一個程序,也經常用來測試開發、編譯環境是否能夠正常工作。提示:「Hello,World!」中間沒空格。【輸入】無【輸出】Hello,World!【輸入樣例】(無)【輸出樣例】Hello,World!
  • 細談c+對c增強
    以上c代碼c編譯器編譯可通過,c++編譯器無法編譯通過 2、c++對結構體的增強 1、c中定義結構體變量需要加上struct關鍵字,c++不需要 2、c中的結構體只能定義成員變量
  • python3使用ctypes在windows中訪問C和C++動態連結庫函數示例
    2 創建VS的win32項目,選擇DLL庫的空項目,也可以選擇導出符號,他會幫你把動態庫的一些宏做好。3 創建testctypes.cpp代碼其中extern "C"表示將此函數編譯為c語言函數,如果你源碼是.c也就是用c語言編譯則不需要加extern "C"。
  • 嵌入式編程感想:會用C語言的是真男人!
    看來,這句話不僅反映了C語言的高難度,也顯示對較年輕的設計工程師來說,C語言真的不好掌握。  對現代的計算機科系學生來說,學習C語言就像選修一門用拉丁文來上的課那樣;但C語言並不是已經過時的設計語言,且在方興未艾的嵌入式軟體開發領域維持著主導地位。下面的圖一是美國版EETimes子網站Embedded Systems Design十三年來針對嵌入式軟體所應用之設計語言的調查統計表。
  • C/C++可變參數函數
    c/c++支持可變參數的函數,即函數的參數是不確定的。一、為什麼要使用可變參數的函數?一般我們編程的時候,函數中形式參數的數目通常是確定的,在調用時要依次給出與形式參數對應的所有實際參數。但在某些情況下希望函數的參數個數可以根據需要確定,因此c語言引入可變參數函數。這也是c功能強大的一個方面,其它某些語言,比如fortran就沒有這個功能。典型的可變參數函數的例子有大家熟悉的printf()、scanf()等。二、c/c++如何實現可變參數的函數?
  • c語言中的字符串操作函數
    一,常量指針與指針常量先來補充一個上篇文章 手把手教你深入理解c/c++中的指針 我在講述指針中的一個問題,有人說常量指針與指針常量這兩個概念總是混淆怎麼辦,例如:int a = 100;const int * p = &a;  int const * p = &a;