C與C++的兼容性

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

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

C++的創始人BjarneStroustrup建議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++里是不合法的。ISOC++附錄C.1中列出了這些區別。

- 一個常見的區別是在C語言中的指針類型更加弱類型。具體來說，C語言允許將`void*`指針賦值給任何類型的指針而無需強制轉換，而C++則不允許；這個習慣用法經常出現在使用`mallo`C管理記憶體的C代碼中，或POSIX線程庫的上下文傳遞以及其他涉及回調的框架。例如，下面這些從`void *`到`int *`的隱式轉換例子在C語言中是可行的，但在C++中不允許:

```c
void *ptr;
int *i = ptr;
```

或類似的：

```c
int *j = malloc(5 * sizeof *j);
```

為了使代碼在C和C++中同時可用，必須使用強制類型轉換，如下所示（然而這樣在兩種語言中都會報警告）：

```c++
void *ptr;
int *i = (int *)ptr;
int *j = (int *)malloc(5 * sizeof *j);
```

C++中提供了其它方法來實現指針類型轉換。C++不推薦繼續使用老式類型轉換。老式類型轉換在形式上來說不是特別清晰，容易被忽略。

```c++
void *ptr;
auto i = reinterpret_cast<int *>(ptr);
auto j = new int[5];
```

- C語言允許在安全的前提下隱式添加除`const`和`volatile`的關鍵字，而C++則不會。
- C++為C語言的部分函數添加了一個參數帶`const`的重載，例如`strchr`在C語言中的聲明為char *strchr(char *)，而在C++中則有一個重載函數為`constChar *strchr(constChar *)`。
- C++在枚舉方面也更加嚴格。在C++中，`int`不能被隱式轉換為枚舉類型。因為在C++標準中並沒有規定枚舉的類型為`int`（在C語言標準中則規定了），並且大小可能與C++中`int`的大小不同。從C++11開始，C++允許程式設計師將自定義的整數類型賦值給枚舉類型。
- 在C++中，const變量必須被初始化，但在C語言中不必：

```c++
const int var1;
const int var2 = 1;
```

第一行代碼在C語言中是允許的（在某些編譯器中可能會報告「Warning」），但在C++中會報錯；第二行代碼在C和C++中都是合法的。

- C++不允許goto和標籤之間出現初始化語句。如下示例所示，該段代碼在C語言中是允許的，但在C++中不允許。

```c
void fn(void)
{
    goto flack;
    int i = 1;
flack:
    ;
}
```

- 雖然語法上有效，但如果跳過的堆棧幀包含具有非平凡（nontrivial）析構函數的對象（即指針），則longjmp函數會在C++中被認為是未定義的操作。C++可以自由定義此時的行為，以便能夠調用析構函數。但是，這樣會使longjump的一些用法失效，否則就可以通過在單獨的調用堆棧之間進行longjmp來實現線程或協程——在全局地址空間中從較低的調用堆棧跳轉到較高的調用堆棧時，將析構函數用於較低調用堆棧中的每個對象。這個問題在C語言中不存在。
- C語言中允許使用與用struct，enum和union類型名稱相同的名稱定義新結構體。

```c
enum BOOL {FALSE, TRUE};
typedef int BOOL;
```

- 在C語言中，由struct，enum或union創建的類型在聲明變量時必須在自定義的類型前加struct，enum或union（取決於定義時的類型），而在C++中不必。因為自定義類型在創建時已隱含typedef（這也是上一點區別形成的原因）。

```c
struct foo{
        int bar;
};
/*
在C++中，以上形式被隐式转换为：
typedef struct foo{
        int bar;
} foo;
*/
struct foo foobar;//在C与C++中均可
foo foobar2; //在C语言中不允许，在C++中允许
```

- 在C++中不允許使用舊式K&R聲明（如下所示），這種聲明在C語言中仍然可用，但是標準已將這種聲明方式列為「過時」（「過時」是在ISOC標準中定義的術語，它表示委員會可以考慮在未來的標準中將其刪除）。

```c
int add(a,b)
int a;
int b;
{
    return a+b;
}
```

- 在C99標準出現之前，C語言允許隱式函數聲明（即省略函數聲明），而C++不允許；但在C99標準出現及以後，C與C++均不允許使用這種形式。
- 在C語言中，如果函數原型中沒有參數（如int foo，表明該函數的參數不確定（這是一種標準不推薦的用法）；而在C++中相同的形式等同於int foo(void);。如果想要在C語言中表示沒有參數，應該使用int foo(void);。
- 在C和C++中，都可以嵌套定義`struct`類型，如下所示：

```c
struct foo{
    struct bar{
        int x;
    };
    int y;
}
```

然而，在使用時，C和C++採用不同的方法，假設此時已有`struct foo`類型變量a，且`x = 1，y = 2;`如要將x賦值給變量b，c：

```c
//以下是C语言方法,在C++中不允许：
int b = a.x;

//以下是C++特有方法，在C中不允许:
int c = a::x
```

- C99和C11添加了一些未包含在標準C++中的附加功能，例如複數，變長陣列（值得注意的是，在C99中列為強制要求的VLA（變長陣列）和複數在C11中被列為了可選項，這是由於部分特殊平台的C編譯器無法有效率地實現或無法實現這兩個功能導致的。目前基本所有平台都有對應的可使用VLA的編譯器），靈活陣列類型（Flexible array member，也叫伸縮型陣列成員），restrict關鍵字，陣列參數限定詞，複合文字，指定初始化項目，可變參數宏，附加數學庫，預定義的標識符（如__func__)和可移植整數類型。
- C99中通過內建的關鍵字_Complex和由它定義的宏complex實現了虛數類型——`floatComplex`和`doubleComplex`；而C++通過一種完全不同的方式實現了它——使用虛數庫。這兩種方式是不兼容的。
- VLA可能導致`sizeof`的值在運行時才能確定。

```c
void foo(size_t x, int a[*]);  // 使用VLA的函数
void foo(size_t x, int a[x]) 
{
    printf("%zu\n", sizeof a); // 等同于sizeof(int*)
    char s[x*2];
    printf("%zu\n", sizeof s); // 将显示x*2
}
```

- 在C99中，如果一個結構體有多個變量且它的最後一個變量為陣列，則它可以是一個柔性陣列。它與VLA類似，但VLA不能出現在定義中。它不指定陣列的長度。截止到C++20，C++中沒有類似的功能。下面是它的一個例子：

```C
struct X
{
    int n, m;
    char bytes[];
}
```

- 截止到C++20標準，`restrict`類型限定符並沒有出現在C++里；但是很多編譯器都提供了對它的支持（如GCC，MSVC，ICC，Clang等）。
- 函數聲明中的陣列類型限定符在C++中是不允許的，下面是一個例子：

```c
int foo(int a[const]);     // 相当于int foo(int *const a);
int bar(char s[static 5]); // 表示s的长度至少为5
```

- C中的複合文字特性在C++中以一種叫做「列表初始化」的方法被實現——雖然它們在意義和作用上是不同的，但它們通常能起到相同的效果。

```c
struct X a = (struct X){4, 6};  // 在C++ 中等于 X{4, 6}. 
```

- 從C99開始，C語言支持結構的指定值初始化；在C++20以前，這是不允許的；但是從C++20標準開始，C++也支持了這個特性。

```C
struct X a = {.n = 4, .m = 6};  
char s[20] = {[0] = 'a', [8]='g'}; 
```

- 在C++中，可以使用「`noreturn`」來標記一個沒有返回值的函數，但在C11以前沒有這樣一個關鍵字。在C11中新增了關鍵字「`_Noreturn`」。
- C語言允許在函數原型中聲明複合數據類型，而C++不允許。
- C++較C語言增加了一些關鍵字，這使得如果一段C語言代碼使用了C++中新增的關鍵字作為標識符，它將會是非法的。

```c
struct template 
{
    int new;
    struct template* class;
};
```

以上代碼在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提供了頭文件，其中一部分代碼如下（選自GCC編譯器，著作權資訊在注釋中）：

```c
/* Copyright (C) 1998-2020 Free Software Foundation, Inc.

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  */

/*
 * ISO C Standard:  7.16  Boolean type and values  <stdbool.h>
 */
#ifndef __cplusplus

#define bool	_Bool
#define true	1
#define false	0
#else

...
```

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

```C
extern int T;

int size(void)
{
    struct T {  int i;  int j;  };
    
    return sizeof(T);
    /* C:   return sizeof(int)
     * C++: return sizeof(struct T)
     */
}
```

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

## 外部連結

- DetailedComparison，C89標準與C++標準逐句比較
- Incompatibilities Between ISOCand ISOC++，David R. Tribble (2001-08).
- Oracle (Sun Microsystems)C++Migration Guide， section 3.11，Sun編譯器有關連結的文檔
- Oracle: MixingCandC++Code in the Same Program，由SteveClamage（ANSIC++委員會主席）概述。