好多博客都說c++是C語言的超集,但c++又不能完全兼容C語言,那不兼容的那部分到底是什么?
C++ 可以不是我們理解上的 C Plus,更不是 C 的超集。
20 多年前,在大學里,我先學的 C 語言,后學的 C++,C語言的時候沒有學好,學C++的時候更是那樣,后來我和好多同學一樣 C++ 的作業(yè),基本上都是用 C 語言的代碼完成的。
如果那時有人告訴我,C++ 就是C語言的延續(xù)我都信,但是后來用的多了,也見的多了,我了解到了一個真相:C++ 只是擁抱 C 的乖孩子,而那些難馴的特性,早就給拉黑了。
曾經(jīng),我信了那句話「99% 的 C 代碼能在 C++ 里跑」,是因為我就是這么干的,這話不僅聽著舒服,也讓很多新人走進了舒適區(qū),甚至我都忽略了那 1%,但 ta 著實害人不淺。
![]()
今天我就用自己被折磨的經(jīng)歷,來告訴你:那 1% 到底是什么?為什么 C++ 寧可讓你重寫,也不肯兼容?
一、C 有、C++ 標準根本不認的語法
先說清楚:以下特性,在C++ 標準中完全非法,除非開擴展,否則編譯不過。
![]()
1、變長數(shù)組
C99 允許:
void decode_tag_stream(int n) {uint8_t buffer[n]; // n 來自動態(tài)解析 read_stream(buffer, n);}這在嵌入式場景極其高效,零堆分配、自動析構(gòu)、緩存友好。
但 C++ 直接拒絕:
// error: array size must be constant expression uint8_t buffer[n];有人說用 vector 啊。
可以,但 vector 要堆分配、要初始化、有額外控制塊。
在我們解析百萬級數(shù)據(jù)流時,VLA 比 vector 快 3 倍。C++寧可犧牲性能,也要守住類型必須在編譯期確定的底線。
2、復合字面量
C 可以構(gòu)造臨時結(jié)構(gòu)體,比如
struct Point { int x, y; };move_to((struct Point){10, 20});也可以造臨時數(shù)組:
int* p = (int[]){1, 2, 3, 4};但這在 C++ 標準是完全不支持的。
雖然 GCC/Clang 在默認模式下允許,但只要你加上 -pedantic-errors,就會立刻報錯。
這意味著:你的代碼在 A 機器能跑,在 B 機器就掛,這根本不是工程,是賭博。
C++11 起可以用 Point{10,20},但無法表達指向臨時數(shù)組的指針這種 C 風格技巧。
3、_Generic 函數(shù)重載
C11 用 _Generic 模擬函數(shù)重載:
#define sqrt(X) _Generic((X), \ float: sqrtf, \ double: sqrt, \ long double: sqrtl)(X)但 C++ 中有模板和重載,根本不需要這種宏。
4、靈活數(shù)組成員
C 允許:
struct Packet {uint32_t len;uint8_t data[]; // flexible array member };分配時:
struct Packet* p = malloc(sizeof(*p) + payload);這是網(wǎng)絡(luò)協(xié)議、二進制日志解析的常用模式
但 C++中禁止使用空數(shù)組。
我們雖然可以用 char data[1] 配合 over-allocation 進行模擬,但這破壞了 RAII,且無法被智能指針管理。
在 C++23 中雖然引入了未知邊界數(shù)組,但那需要特定的上下文,還遠未達到 FAM 的自由度。
5、restrict
C 用 restrict 告訴編譯器這個指針不 alias:
void matmul(float* restrict C, const float* restrict A, const float* restrict B);但C++ 標準中根本沒有這玩意。
雖然 GCC/Clang 支持 __restrict,MSVC 有 __declspec(restrict),但并沒有統(tǒng)一標準。
你若在跨平臺庫中用它,就是在給自己挖坑。
二、同樣寫法,C 和 C++ 行為卻背道而馳
更可怕的是:代碼能編譯,但運行結(jié)果不一樣。這才是最可怕之處。
![]()
1、void* 隱式轉(zhuǎn)換
C 允許:
void* ptr = malloc(100);int* p = ptr; // 隱式轉(zhuǎn)換,合法C++ 直接報錯:
int* p = ptr; // error: invalid conversion from ‘void*’ to ‘int*’ 必須寫:
int* p = static_cast(ptr);這事雖小,但在混合工程中,一個漏掉的 cast 足以讓整個模塊編譯失敗。
因此 C++ 寧可讓你多敲幾下鍵盤,也不讓你繞過類型系統(tǒng)。
2、字符常量的類型
C 中:
sizeof('a') == sizeof(int) // true C++ 中:
sizeof('a') == sizeof(char) // true 在普通函數(shù)中,兩者行為一致。但在模板推導、decltype、泛型宏中,差異就會產(chǎn)生:
template void f(T);f('a'); // C++ 中 T = char,C 中若模擬,可能推為 int 這種差異在通用庫中,如序列化框架,極易觸發(fā)怪異的 bug。
3、const 全局變量的鏈接屬性
C 中:
const int MAX_TAGS = 1000; // external linkageC++ 中:
const int MAX_TAGS = 1000; // internal linkage,相當于 static 可以這么了解,你如果有一個 C 頭文件定義了 const int VERSION = 1;,多個 C 文件包含沒問題;但一旦被 C++ 包含,每個編譯單元都會生成自己的副本。若期望外部鏈接,比如用于地址比較,將直接失效。
現(xiàn)代 C 項目中通常加 static 避免此問題。
4、老式函數(shù)聲明
C 中允許,C++ 中就是語法錯誤。
int add(a, b)int a, b;return a + b;}更隱蔽的是隱式函數(shù)聲明:
int main() {foo(); // C 假設(shè)返回 int }C++ 要求所有函數(shù)必須先聲明。這在鏈接第三方 C 庫時尤為重要,C 能僥幸通過,C++ 直接卡死。
三、混用 C 與 C++的正確姿勢
混用 C與C++,有 3 條原則
1、C 頭文件必須用 extern "C" 包裹
extern "C" { #include "legacy_sensor_driver.h" }2、.c 不改成 .cppC 開發(fā)的源文件,用 C 編譯器編譯,再鏈接到 C++。
3、不依賴編譯器擴展如復合字面量、__restrict 等,除非你能 100% 控制所有部署環(huán)境。
![]()
?? C++ 不是 C 語言的簡單升級
C++ 不是 C 語言簡單的版本升級,C++ 放棄了 C 語言中的一些自由,卻換來了在大型系統(tǒng)中的可維護性、可驗證性和安全性的提升。
以上,是我在使用這兩種編程語言后的認識,你呢?
你在移植 C 代碼到 C++ 時,有沒有被那些所謂的1%坑過沒?
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.