C與C++的相容性

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

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

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

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

在C中允許而在C++中不允許存在的代碼陳述式

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

  • 一個常見的區別是在C語言中的指標類型更加弱型別。具體來說,C語言允許將void* 指標賦值給任何類型的指標而無需強制轉換,而C++則不允許;這個習慣用法經常出現在使用malloc 管理主記憶體的C代碼中,[9]POSIX線程庫的上下文傳遞以及其他涉及回呼的框架。例如,下面這個例子在C語言中是可行的,但在C++中不允許:
    void *ptr;
    /*从void *到int *的隐式转换*/
    int *i = ptr;
    
    或類似的:
    int *j = malloc(5 * sizeof *j);     /*从void *到int *的隐式转换 */
    
    為了使代碼在C和C++中同時可用, 必須使用強制類型轉換, 如下所示(然而這樣在兩種語言中都會報警告[10][11]):
    void *ptr;
    int *i = (int *)ptr;
    int *j = (int *)malloc(5 * sizeof *j);
    
    C++中提供了其它方法來實現指標類型轉換。C++不推薦繼續使用老式類型轉換。老式類型轉換在形式上來說不是特別清晰,容易被忽略。[12]
    void *ptr;
    auto i = reinterpret_cast<int *>(ptr);
    auto j = new int[5];
    
  • C語言允許在安全的前提下隱式添加除constvolatile的關鍵字,而C++則不會。
  • C++為C語言的部分函數添加了一個參數帶const多載,例如strchrC語言中的聲明為char *strchr(char *),而在C++中則有一個多載函數為const char *strchr(const char *)
  • C++在列舉方面也更加嚴格。在C++中,int不能被隱式轉換為列舉類型。因為在C++標準中並沒有規定列舉的類型為int(在C語言標準中則規定了),並且大小可能與C++中int的大小不同。從C++11開始,C++允許程式設計師將自訂的整數類型賦值給列舉類型。
  • 在C++中,const變數必須被初始化,但在C語言中不必:
    const int var1;
    const int var2 = 1;
    
    第一行代碼在C語言中是允許的(在某些編譯器中可能會報告「Warning」),但在C++中會報錯;第二行代碼在C和C++中都是合法的。
  • C++不允許goto和標籤之間出現初始化陳述式。如下範例所示,該段代碼在C語言中是允許的,但在C++中不允許。
    void fn(void)
    {
        goto flack;
        int i = 1;
    flack:
        ;
    }
    
  • 雖然語法上有效,但如果跳過的堆疊幀包含具有非平凡(nontrivial)解構函式的對象(即指標),則longjmp()函數會在C++中被認為是未定義的操作。[13] C++可以自由定義此時的行為,以便能夠呼叫解構函式。但是,這樣會使longjump()的一些用法失效, 否則就可以通過在單獨的呼叫堆疊之間進行longjmp來實現線程協程——在全域地址空間中從較低的呼叫堆疊跳轉到較高的呼叫堆疊時,將解構函式用於較低呼叫堆疊中的每個對象。這個問題在C語言中不存在。
  • C語言中允許使用與用structenumunion類型名稱相同的名稱定義新結構體。
    enum BOOL {FALSE, TRUE};
    typedef int BOOL;//在C语言中允许,在C++中不允许
    
  • C語言中,由structenumunion建立的類型在聲明變數時必須在自訂的類型前加structenumunion(取決於定義時的類型),而在C++中不必。因為自訂類型在建立時已隱含typedef(這也是上一點區別形成的原因)。
    struct foo{
            int bar;
    };
    /*
    在C++中,以上形式被隐式转换为:
    typedef struct foo{
            int bar;
    } foo;
    */
    struct foo foobar;//在C与C++中均可
    foo foobar2; //在C语言中不允许,在C++中允许
    
  • 在C++中不允許使用舊式K&R聲明(如下所示),這種聲明在C語言中仍然可用,但是標準已將這種聲明方式列為「過時」(「過時」是在ISO 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類型,如下所示:
    struct foo{
        struct bar{
            int x;
        };
        int y;
    }
    
    然而,在使用時,C和C++採用不同的方法,假設此時已有struct foo類型變數a, 且x = 1,y = 2;如要將x賦值給變數b,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關鍵字,陣列參數限定詞,複合文字英語C_syntax#Compound_literals,指定初始化專案,可變參數宏,附加數學庫,預定義的識別碼(如__func__)和可移植整數類型[14]
  • C99中通過內建的關鍵字_Complex和由它定義的宏complex實現了虛數類型——float complexdouble complex;而C++通過一種完全不同的方式實現了它——使用虛數庫。這兩種方式是不相容的。
  • VLA可能導致sizeof的值在執行時才能確定。[15]
    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++中沒有類似的功能。下面是它的一個例子:
    struct X
    {
        int n, m;
        char bytes[];
    }
    
  • 截止到C++20標準,restrict類型限定符並沒有出現在C++里;但是很多編譯器都提供了對它的支援(如GCC,MSVC,ICC,Clang等)。
  • 函數聲明中的陣列類型限定符在C++中是不允許的,下面是一個例子:
    int foo(int a[const]);     // 相当于int foo(int *const a);
    int bar(char s[static 5]); // 表示s的长度至少为5
    
  • C中的複合文字特性在C++中以一種叫做「列表初始化」的方法被實現——雖然它們在意義和作用上是不同的,但它們通常能起到相同的效果。
    struct X a = (struct X){4, 6};  // 在C++ 中等于 X{4, 6}.
    
  • 從C99開始,C語言支援結構的指定值初始化;在C++20以前,這是不允許的;但是從C++20標準開始,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++中新增的關鍵字作為識別碼,它將會是非法的。
    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++中,boolfalsetrue作為內建類型關鍵字提供,在實現時,truefalse為「bool」類型。而在C99以前,C語言中沒有布林類型;C99提供了關鍵字_Bool類型,但沒有「true」,「false」關鍵字。為了增加與C++的相容性,C99提供了標頭檔<stdbool.h>[16],其中一部分代碼如下(選自GCC編譯器,著作權資訊在註釋中):
/* 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++和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語言中則不會。


參考資料

  1. ^ 1.0 1.1 Stroustrup, Bjarne. An Overview of the C++ Programming Language in The Handbook of Object Technology (Editor: Saba Zamir). CRC Press LLC, Boca Raton. 1999. ISBN 0-8493-3135-8. (PDF): 4. [2009-08-12]. (原始內容存檔 (PDF)於2012-08-16). 
  2. ^ B.Stroustrup. C and C++: Siblings. The C/C++ Users Journal. July 2002. (PDF). [2019-03-17]. (原始內容存檔 (PDF)於2018-12-21). 
  3. ^ Bjarne Stroustrup's FAQ – Is C a subset of C++?. [22 Sep 2019]. (原始內容存檔於2016-02-06). 
  4. ^ B. Stroustrup. C and C++: A Case for Compatibility. The C/C++ Users Journal. August 2002. (PDF). [18 August 2013]. (原始內容存檔 (PDF)於2012-07-22). 
  5. ^ Rationale for International Standard—Programming Languages—C頁面存檔備份,存於互聯網檔案館), revision 5.10 (2003.04).
  6. ^ Prata, Stephen. C Primer Plus第5版中文版. 北京: 人民郵電出版社. 2005: 12. ISBN 9787115130228. 
  7. ^ N4659: Working Draft, Standard for Programming Language C++ (PDF). §Annex C.1. (原始內容存檔 (PDF)於2017-12-07).  ("It is invalid to jump past a declaration with explicit or implicit initializer (except across entire block not entered). … With this simple compile-time rule, C++ assures that if an initialized variable is in scope, then it has assuredly been initialized.")
  8. ^ N4659: Working Draft, Standard for Programming Language C++ (PDF). §Annex C.1. (原始內容存檔 (PDF)於2017-12-07). 
  9. ^ IBM Knowledge Center. ibm.com. 
  10. ^ FAQ > Casting malloc - Cprogramming.com. (原始內容存檔於2007-04-05). 
  11. ^ 4.4a — Explicit type conversion (casting). 16 April 2015. (原始內容存檔於2016-09-25). 
  12. ^ Lippman, Stanley; Lajoie, Josee; Moo, Barbara. C++ Primer 5th. Addison-Wesley Professional. 2012: 165. ISBN 9780321714114. 
  13. ^ longjmp - C++ Reference. www.cplusplus.com. (原始內容存檔於2018-05-19). 
  14. ^ Prata, Stephen. C Primer Plus 5th. ISBN 0672326965. 
  15. ^ Incompatibilities Between ISO C and ISO C++. (原始內容存檔於2006-04-09). 
  16. ^ ISO/IEC 9899:1999 7.16 (PDF). [2020-07-02]. (原始內容存檔 (PDF)於2018-01-27). 

外部連結