C与C++的兼容性

C 語言和 C++ 的關係相當密切，但是也存在許多顯著的差異。C++ 標準起源於早期 C 標準， 並被設計為與當時的 C 語言在原始碼編寫和連結方面很大程度上相容。 因此，兩種語言的開發工具（例如 IDE 和編譯器）通常被整合到單個產品中，程式設計師可以自己選擇編寫的是 C 還是 C++，開發工具通常會根據程式設計師的選擇使用不同的編譯器和連結器或不同的庫。

但是，C 並不是 C++ 的子集， 一般的 C 代碼不經修改很難被一些嚴格符合 C++ 標準的 C++ 編譯器成功編譯；同樣，C++ 引入了許多 C 中沒有的特性，所以，幾乎所有用 C++ 編寫的代碼都無法被 C 編譯器成功編譯。在這篇文章中，我們主要討論的是它們在公共部分的差異，比如在 C 語言中合法的代碼到了 C++ 中成為了不合法的代碼，或一段代碼在 C 和 C++ 中表現出不同的行為。

C++ 的創始人 Bjarne Stroustrup 建議 C 和 C++ 應該儘可能減小差異，以最大限度地提高這兩種語言的相容性；而另一些人則認為 C 和 C++ 畢竟是兩種不同的語言 —— 雖然 C++ 起源於 C—— 因此它們之間的相容性並不是那麼重要。 而 ANSI 是這樣看的：「我們贊同保持 C 與 C++ 的最大公共子集原則」，同時「保持它們的差別，使這兩種語言繼續獨立發展」，「…… 委員會希望 C++ 成為重要的和強有力的語言。」

截止到 C++20 和 C2x，C++ 還是不支援部分 C 語言特性，如變長陣列，原生複數支援和 `restrict` 類型修飾詞。另一方面，與 C89 相比，C99 通過合併 C++ 功能（例如 `//` 注釋，允許聲明出現在代碼中而不只是在函式頭）減少了一些其他不相容性。

## 在 C 中允許而在 C++ 中不允許存在的代碼語句

C++ 較 C 語言有更嚴格的類型轉換和初始化規則 ， 因此一些在 C 語言里合法的語句在 C++ 里是不合法的。ISO C++ 附錄 C.1 中列出了這些區別。

* 一個常見的區別是在 C 語言中的指標類型更加弱型別。具體來說，C 語言允許將 `void*` 指標賦值給任何類型的指標而無需強制轉換，而 C++ 則不允許；這個習慣用法經常出現在使用 `malloc` 管理記憶體的 C 代碼中， 或 POSIX 執行緒庫的上下文傳遞以及其他涉及回呼的框架。例如，下面這個例子在 C 語言中是可行的，但在 C++ 中不允許：或類似的：為了使代碼在 C 和 C++ 中同時可用， 必須使用強制類型轉換， 如下所示（然而這樣在兩種語言中都會報警告）：C++ 中提供了其它方法來實現指標類型轉換。C++ 不推薦繼續使用老式類型轉換。老式類型轉換在形式上來說不是特別清晰，容易被忽略。。
* C 語言允許在安全的前提下隱式添加除 `const` 和 `volatile` 的關鍵字，而 C++ 則不會。
* C++ 為 C 語言的部分函式添加了一個參數帶 `const` 的多載，例如 `strchr` 在 C 語言中的聲明為 `char *strchr(char *)`，而在 C++ 中則有一個多載函式為 `const char *strchr(const char *)`。
* C++ 在列舉方面也更加嚴格。在 C++ 中，`int` 不能被隱式轉換為列舉類型。因為在 C++ 標準中並沒有規定列舉的類型為 `int`（在 C 語言標準中則規定了），並且大小可能與 C++ 中 `int` 的大小不同。從 C++11 開始，C++ 允許程式設計師將自訂的整數類型賦值給列舉類型。
* 在 C++ 中，const 變數必須被初始化，但在 C 語言中不必：第一行代碼在 C 語言中是允許的（在某些編譯器中可能會報告「Warning」），但在 C++ 中會報錯；第二行代碼在 C 和 C++ 中都是合法的。
* C++ 不允許 `goto` 和標籤之間出現初始化語句。如下範例所示，該段代碼在 C 語言中是允許的，但在 C++ 中不允許。
* 雖然語法上有效，但如果跳過的堆疊框包含具有非平凡（nontrivial）解構函式的物件（即指標），則 `longjmp()` 函式會在 C++ 中被認為是未定義的操作。 C++ 可以自由定義此時的行為，以便能夠呼叫解構函式。但是，這樣會使 `longjump()` 的一些用法失效， 否則就可以通過在單獨的呼叫堆疊之間進行 longjmp 來實現執行緒或協程 —— 在全域位址空間中從較低的呼叫堆疊跳轉到較高的呼叫堆疊時，將解構函式用於較低呼叫堆疊中的每個物件。這個問題在 C 語言中不存在。
* C 語言中允許使用與用 `struct`，`enum` 和 `union` 類型名稱相同的名稱定義新結構體。
* 在 C 語言中，由 `struct`，`enum` 或 `union` 建立的類型在聲明變數時必須在自訂的類型前加 `struct`，`enum` 或 `union`（取決於定義時的類型），而在 C++ 中不必。因為自訂類型在建立時已隱含 typedef（這也是上一點區別形成的原因）。
* 在 C++ 中不允許使用舊式 K&R 聲明（如下所示），這種聲明在 C 語言中仍然可用，但是標準已將這種聲明方式列為「過時」（「過時」是在 ISO C 標準中定義的術語，它表示委員會可以考慮在未來的標準中將其刪除）。
* 在 C99 標準出現之前，C 語言允許隱式函式聲明（即省略函式聲明），而 C++ 不允許；但在 C99 標準出現及以後，C 與 C++ 均不允許使用這種形式。
* 在 C 語言中，如果函式原型中沒有參數（如 `int foo();`，表明該函式的參數不確定（這是一種標準不推薦的用法）；而在 C++ 中相同的形式等同於 `int foo(void);`。如果想要在 C 語言中表示沒有參數，應該使用 `int foo(void);`。
* 在 C 和 C++ 中，都可以巢狀定義 `struct` 類型，如下所示：然而，在使用時，C 和 C++ 採用不同的方法，假設此時已有 struct foo 類型變數 a， 且 x = 1，y = 2；如要將 x 賦值給變數 b，c：
* C99 和 C11 添加了一些未包含在標準 C++ 中的附加功能，例如複數，變長陣列（值得注意的是，在 C99 中列為強制要求的 VLA（變長陣列）和複數在 C11 中被列為了可選項，這是由於部分特殊平台的 C 編譯器無法有效率地實現或無法實現這兩個功能導致的。目前基本所有平台都有對應的可使用 VLA 的編譯器），靈活陣列類型（Flexible array member，也叫伸縮型陣列成員），`restrict` 關鍵字，陣列參數限定詞，複合文字，指定初始化專案，可變參數宏，附加數學庫，預定義的識別碼（如 \_\_func\_\_) 和可移植整數類型。
* C99 中通過內建的關鍵字 \_Complex 和由它定義的宏 complex 實現了虛數類型 ——`float complex` 和 `double complex`；而 C++ 通過一種完全不同的方式實現了它 —— 使用虛數庫。這兩種方式是不相容的。
* VLA 可能導致 `sizeof` 的值在執行時才能確定。
* 在 C99 中，如果一個結構體有多個變數且它的最後一個變數為陣列，則它可以是一個柔性陣列。它與 VLA 類似，但 VLA 不能出現在定義中。它不指定陣列的長度。截止到 C++20，C++ 中沒有類似的功能。下面是它的一個例子：
* 截止到 C++20 標準，`restrict` 類型限定符並沒有出現在 C++ 里；但是很多編譯器都提供了對它的支援（如 GCC，MSVC，ICC，Clang 等）。
* 函式聲明中的陣列類型限定符在 C++ 中是不允許的，下面是一個例子：
* C 中的複合文字特性在 C++ 中以一種叫做「列表初始化」的方法被實現 —— 雖然它們在意義和作用上是不同的，但它們通常能起到相同的效果。
* 從 C99 開始，C 語言支援結構的指定值初始化；在 C++20 以前，這是不允許的；但是從 C++20 標準開始，C++ 也支援了這個特性。
* 在 C++ 中，可以使用「`noreturn`」來標記一個沒有返回值的函式，但在 C11 以前沒有這樣一個關鍵字。在 C11 中新增了關鍵字「`_Noreturn`」。
* C 語言允許在函式原型中聲明複合資料類型，而 C++ 不允許。
* C++ 較 C 語言增加了一些關鍵字，這使得如果一段 C 語言代碼使用了 C++ 中新增的關鍵字作為識別碼，它將會是非法的。以上代碼在 C 語言中是允許的，但是在 C++ 中不允許。

## 在 C 和 C++ 中行為不同的語句

有一些語句在 C 和 C++ 中都有效，但是在兩種語言中會產生不同的結果。

* `char` 類型在 C 語言中的大小等於 int 類型的大小，而在 C++ 中的大小就是「`char`」類型的大小，即 1 位元組。因此當有語句 `sizeof(char)` 時，在 C 語言中的結果等於 `sizeof(int)`，在 C++ 中的結果為 1。而且這種差異導致在 C 語言中，無論聲明形式為 `signed char` 還是 `unsigned char`，其所聲明的都是一個有符號表達式，而在 C++ 中，這取決於實現。
* 在 C++ 中，一個 `const` 變數除非被顯式聲明為 `extern`，否則這個變數將為內部連結，這與 C `extern` 語言預設變數作用域為檔案不同。實際上，這不會導致相同的 C 和 C++ 代碼產生不同的結果，而是會導致編譯時或連結錯誤。
* 在 C 語言中，如果不希望在一個檔案中連結到一個不再此檔案中的行內函式，則必須在函式聲明中加 `extern`；而 C++ 則會自動處理這些問題。更詳細地，C 語言有兩種 inline 函式定義：普通的外部定義（即顯式添加 `extern`）和行內版本。另一方面，C++ 僅為行內函式提供行內定義。在 C 語言中，行內定義與 static 定義類似，因為它可以與其他檔案中有檔案作用域的同名函式或同一翻譯單元中任意數量的相同名稱的行內或普通函式共存，這是合法的。當這些函式對編譯器可見時，C 編譯器會選擇一個函式（這取決於實現）。而在 C++ 中，若一個 inline 函式具有外部作用域，則它在所有檔案中的聲明都必須相同。最後，靜態行內函式在 C 和 C++ 中的行為相同。
* C 和 C++ 中都有布林類型，但是它們有不同的定義。在 C++ 中，`bool`，`false`，`true` 作為內建類型關鍵字提供，在實現時，`true` 和 `false` 為「`bool`」類型。而在 C99 以前，C 語言中沒有布林類型；C99 提供了關鍵字 `_Bool` 類型，但沒有「`true`」，「`false`」關鍵字。為了增加與 C++ 的相容性，C99 提供了標頭檔 `<stdbool.h>`，其中一部分代碼如下（選自 GCC 編譯器，著作權資訊在注釋中）：

需要注意的是，`_Bool` 在 C 語言中被儲存為 int 類型。

* 下面這個函式在 C++ 和 C 中返回不同的值：

這是因為在 C 語言中，struct（結構體）類型前需要添加「`struct`」，而在這個例子中，因為 `T` 前沒有 `struct`，所以它所代表的是在外部定義的 int 變數；而在 C++ 中，因為可以省略 `struct`，因此造成了歧義。然而，在 C++ 中如果使用 `sizeof T` 這種形式，編譯器會更傾向於認為 T 是一個表達式，因此編譯會出錯；而在 C 語言中則不會。

 

## 參考資料

## 外部連結

* Detailed comparison（頁面存檔備份，存於網際網路檔案館）， C89 標準與 C++ 標準逐句比較
* Incompatibilities Between ISO C and ISO C++（頁面存檔備份，存於網際網路檔案館）， David R. Tribble (2001-08).
* Oracle (Sun Microsystems) C++ Migration Guide， section 3.11（頁面存檔備份，存於網際網路檔案館）， Sun 編譯器有關連結的文件
* Oracle: Mixing C and C++ Code in the Same Program（頁面存檔備份，存於網際網路檔案館）， 由 Steve Clamage（ANSI C++ 委員會主席）概述。