Kompatibilita C a C ++ - Compatibility of C and C++

Mezi C a C ++ programovací jazyky jsou úzce souvisí, ale mají mnoho významných rozdílů. C ++ začínal jako vidlička raného, ​​předem standardizovaného C a byl navržen tak, aby byl z velké části kompatibilní se zdrojovými a linkovými kompilátory té doby. Z tohoto důvodu jsou vývojové nástroje pro dva jazyky (například IDE a kompilátory ) často integrovány do jednoho produktu, přičemž programátor může jako zdrojový jazyk určit C nebo C ++.

Nicméně, C není podmnožina C ++ a netriviální C programy nebudou sestavovat jako C ++ kód bez úprav. Podobně C ++ zavádí mnoho funkcí, které nejsou k dispozici v C a v praxi téměř veškerý kód napsaný v C ++ nevyhovuje C kódu. Tento článek se však zaměřuje na rozdíly, které způsobují, že vyhovující kód C je špatně vytvořený v jazyce C ++ nebo že je v obou jazycích vyhovující/dobře tvarovaný, ale v C a C ++ se chová odlišně.

Bjarne Stroustrup , tvůrce C ++, navrhl, aby byla nekompatibilita mezi C a C ++ co nejvíce omezena, aby se maximalizovala interoperabilita mezi těmito dvěma jazyky. Jiní tvrdili, že protože C a C ++ jsou dva různé jazyky, kompatibilita mezi nimi je užitečná, ale ne zásadní; podle tohoto tábora by snahy o snížení neslučitelnosti neměly bránit pokusům o zlepšení každého jazyka izolovaně. Oficiální zdůvodnění standardu 1999 C ( C99 ) „podporuje [d] zásadu zachování největší společné podmnožiny„ mezi C a C ++ “při zachování rozdílu mezi nimi a umožnění jejich samostatného vývoje“ a uvedl, že autoři byli „Obsah, aby byl C ++ velkým a ambiciózním jazykem.“

Několik přírůstků C99 není podporováno v aktuálním standardu C ++ nebo je v rozporu s funkcemi C ++, jako jsou pole s proměnnou délkou , typy nativních komplexních čísel a restrict kvalifikátor typu . Na druhou stranu C99 snížil některé další nekompatibility ve srovnání s C89 začleněním funkcí C ++, jako jsou //komentáře a smíšené deklarace a kód.

Konstrukce platné v C, ale ne v C ++

C ++ vynucuje přísnější pravidla psaní (žádná implicitní porušení systému statického typu) a požadavky na inicializaci (vynucení při kompilaci, že proměnné v rozsahu nemají inicializaci podvrácenou) než C, a proto je některý platný kód C v C ++ neplatný. Jejich zdůvodnění je uvedeno v příloze C.1 normy ISO C ++.

  • Jeden běžně setkáváme rozdíl C je více slabě-napsaný ohledně ukazatelů. Konkrétně C umožňuje void*přiřazení ukazatele libovolnému typu ukazatele bez přetypování, zatímco C ++ nikoli; tento idiom se často objevuje v kódu C pomocí mallocalokace paměti nebo při předávání kontextových ukazatelů rozhraní API POSTH pthreads API a dalších rámců zahrnujících zpětná volání . Například v C, ale ne v C ++ platí následující:
    void *ptr;
    /* Implicit conversion from void* to int* */
    int *i = ptr;
    

    nebo podobně:

    int *j = malloc(5 * sizeof *j);     /* Implicit conversion from void* to int* */
    

    Aby byl kód kompilován jako C i C ++, je třeba použít explicitní přetypování (s některými výhradami v obou jazycích):

    void *ptr;
    int *i = (int *)ptr;
    int *j = (int *)malloc(5 * sizeof *j);
    
  • C ++ má komplikovanější pravidla týkající se přiřazení ukazatelů, která přidávají kvalifikátory, protože umožňují přiřazení int ** k, const int *const *ale nikoli nebezpečnému přiřazení, const int **zatímco C neumožňuje žádné z nich (ačkoli kompilátory obvykle vydají pouze varování).
  • C ++ mění některé standardní funkce knihovny C a přidává další přetížené funkce s const kvalifikátory typů , např. strchrVrací se char*v C, zatímco C ++ funguje, jako by existovaly dvě přetížené funkce const char *strchr(const char *)a char *strchr(char *).
  • C ++ je také přísnější v převodu na výčty: ints nelze implicitně převést na výčty jako v C. Také konstantyenum výčtu ( enumerátory) jsou vždy typu intv C, zatímco v C ++ jsou odlišné typy a mohou mít jinou velikost než ta ze dne int.
  • V C ++ constmusí být inicializována proměnná; v C to není nutné.
  • Kompilátory C ++ zakazují přechod nebo přepnutí z inicializace, jako v následujícím kódu C99:
    void fn(void)
    {
        goto flack;
        int i = 1;
    flack:
        ;
    }
    
  • Je-li syntakticky platný, má za longjmp()následek nedefinované chování v C ++, pokud přeskakované rámce zásobníku obsahují objekty s netriviálními destruktory. Implementace C ++ může volně definovat chování, které by se nazývalo destruktor. To by však vyloučit určité využití longjmp (), která by jinak byla platná, jako je zavádění nití nebo koprogram by longjmping mezi jednotlivými stohy volání - při skákání z dolní do horní části zásobníku volání v prostoru globálních adres, destruktory by byl nazýván pro každý objekt v dolním zásobníku volání. V C. neexistuje žádný takový problém
  • C umožňuje více předběžných definic jedné globální proměnné v jedné překladové jednotce , která je neplatná jako narušení ODR v C ++.
    int N;
    int N = 10;
    
  • V jazyce C, deklarovat nový typ se stejným názvem jako existující struct, unionnebo enumje platný, ale je neplatná v jazyce C ++, neboť v C struct, uniona enumdruhy musí být označeny jako vždy, když je typ odkazovaný zatímco v jazyce C ++, všechna prohlášení těchto typů implicitně nese typedef .
    enum BOOL {FALSE, TRUE};
    typedef int BOOL;
    
  • Deklarace funkcí, které nejsou prototypy (ve stylu „K&R“), jsou v jazyce C ++ neplatné; jsou stále platné v C, i když byly považovány za zastaralé od původní standardizace C v roce 1990. (Termín „zastaralý“ je definovaný termín v normě ISO C, což znamená vlastnost, která „může být zvážena pro stažení v budoucích revizích“ standardu.) Podobně implicitní deklarace funkcí (pomocí funkcí, které nebyly deklarovány) nejsou v C ++ povoleny a v C jsou od roku 1999 neplatné.
  • V C, prototyp funkce bez parametrů, například int foo();znamená, že parametry nejsou specifikovány. Proto je legální volat takovou funkci s jedním nebo více argumenty , např foo(42, "hello world"). Naproti tomu v C ++ prototyp funkce bez argumentů znamená, že funkce nepřijímá žádné argumenty a volání takové funkce pomocí argumentů má špatný tvar. V C je správným způsobem deklarace funkce, která nevyžaduje žádné argumenty, použití 'void', jako v int foo(void);, což je také platné v C ++. Prototypy prázdných funkcí jsou zastaralým prvkem v C99 (jako tomu bylo v C89).
  • V C i C ++ lze definovat vnořené structtypy, ale rozsah je interpretován odlišně: v C ++ je vnořený structdefinován pouze v rámci oboru/oboru názvů vnějšího struct, zatímco v C je vnitřní struktura definována také mimo vnější strukturu.
  • C umožňuje struct, uniona enumtypy, které mají být deklarovány v prototypech funkcí, zatímco C ++ ne.

C99 a C11 přidaly do C několik dalších funkcí, které nebyly začleněny do standardního C ++, například komplexní čísla, pole s proměnnou délkou (komplexní čísla a pole s proměnnou délkou jsou v C11 označována jako volitelná rozšíření), flexibilní členy pole , omezení klíčové slovo, kvalifikátory parametrů pole, složené literály a určené inicializátory .

  • Složitá aritmetika využívající float complexa double complexprimitivní datové typy byla přidána do standardu C99 prostřednictvím makra _Complexklíčových slov a complexpraktičnosti. V C ++ lze složitou aritmetiku provádět pomocí třídy komplexních čísel, ale tyto dvě metody nejsou kompatibilní s kódem. (Standardy od C ++ 11 však vyžadují binární kompatibilitu.)
  • Pole s proměnnou délkou. Tato funkce vede k možné nekompilační časové velikosti operátora.
    void foo(size_t x, int a[*]);  // VLA declaration
    void foo(size_t x, int a[x]) 
    {
        printf("%zu\n", sizeof a); // same as sizeof(int*)
        char s[x * 2];
        printf("%zu\n", sizeof s); // will print x*2
    }
    
  • Poslední člen typu struktury C99 s více než jedním členem může být flexibilní člen pole , který má syntaktickou formu pole s neurčenou délkou. To slouží účelu podobnému maticím s proměnnou délkou, ale VLA se v definicích typů nemohou objevit a na rozdíl od VLA nemají flexibilní členové pole žádnou definovanou velikost. ISO C ++ žádnou takovou funkci nemá. Příklad:
    struct X
    {
        int n, m;
        char bytes[];
    }
    
  • Kvalifikátor restrict typu definovaný v C99 nebyl zahrnut ve standardu C ++ 03, ale většina běžných kompilátorů, jako je GNU Compiler Collection , Microsoft Visual C ++ a Intel C ++ Compiler, poskytuje podobnou funkci jako rozšíření.
  • Kvalifikátory parametrů pole ve funkcích jsou podporovány v C, ale ne v C ++.
    int foo(int a[const]);     // equivalent to int *const a 
    int bar(char s[static 5]); // annotates that s is at least 5 chars long
    
  • Funkce složených literálů v jazyce C je zobecněna na vestavěné i uživatelsky definované typy pomocí syntaxe inicializace seznamu C ++ 11, i když s určitými syntaktickými a sémantickými rozdíly.
    struct X a = (struct X){4, 6};  // The equivalent in C++ would be X{4, 6}. The C syntactic form used in C99 is supported as an extension in the GCC and Clang C++ compilers. 
    foo(&(struct X){4, 6});         // The object is allocated in the stack and its address can be passed to a function. This is not supported in C++.
    
  • Určené inicializátory pro pole jsou platné pouze v C:
    char s[20] = { [0] = 'a', [8] = 'g' };  // allowed in C, not in C++
    
  • Funkce, které se nevracejí, lze komentovat pomocí atributu noreturn v C ++, zatímco C používá odlišné klíčové slovo.

C ++ přidává řadu dalších klíčových slov na podporu svých nových funkcí. To vykreslí kód C pomocí těchto klíčových slov pro identifikátory neplatné v C ++. Například:

struct template 
{
    int new;
    struct template* class;
};
je platný kód C, ale je odmítnut kompilátorem C ++, protože klíčová slova „template“, „new“ a „class“ jsou vyhrazena.

Konstrukty, které se v C a C ++ chovají odlišně

Existuje několik syntaktických konstrukcí, které jsou platné v jazyce C i C ++, ale ve dvou jazycích vytvářejí různé výsledky.

  • Znakové literály , jako 'a'jsou typu intv jazyce C a typu charv jazyce C ++, což znamená, že sizeof 'a'v obou jazycích budou obecně poskytovány různé výsledky: v jazyce C ++ bude 1, zatímco v jazyce C bude sizeof(int). Jako další důsledek tohoto rozdílu typů 'a'bude v jazyce C vždy výraz se znaménkem, bez ohledu na to, zda charjde o podepsaný nebo nepodepsaný typ, zatímco pro C ++ je to specifické pro implementaci kompilátoru.
  • C ++ přiřazuje interní propojení constproměnným s rozsahem oboru názvů, pokud nejsou výslovně deklarovány extern, na rozdíl od C, ve kterém externje výchozí pro všechny entity s rozsahem souborů. Všimněte si, že v praxi to nevede k tichým sémantickým změnám mezi identickým kódem C a C ++, ale místo toho to povede k chybě při kompilaci nebo propojení.
  • V jazyce C použití vložených funkcí vyžaduje ruční přidání prototypové deklarace funkce pomocí klíčového slova extern přesně v jedné překladové jednotce, aby bylo zajištěno propojení neinlinované verze, zatímco C ++ to řeší automaticky. Podrobněji C rozlišuje dva druhy definic inlinefunkcí : běžné externí definice (kde se výslovně používá extern ) a vložené definice. C ++ na druhé straně poskytuje pouze vložené definice pro vložené funkce. V jazyce C je vložená definice podobná interní (tj. Statické) definici, protože může koexistovat ve stejném programu s jednou externí definicí a libovolným počtem interních a vložených definic stejné funkce v jiných překladových jednotkách, z nichž všechny se může lišit. Toto je oddělené uvažování od propojení funkce, ale není to nezávislé. Kompilátorům C je poskytnuto uvážení volit mezi použitím vložených a vnějších definic stejné funkce, pokud jsou obě viditelné. C ++ však vyžaduje, aby byla -li funkce s externím propojením deklarována jako vložená v jakékoli překladové jednotce, musí být takto deklarována (a tedy také definována) v každé překladové jednotce, kde je použita, a aby všechny definice této funkce byly totožné , v návaznosti na ODR. Všimněte si, že statické vložené funkce se chovají stejně v C a C ++.
  • C99 i C ++ mají booleovský typ bool s konstantami truea false, ale jsou definovány odlišně. V C ++ boolje vestavěný typ a vyhrazené klíčové slovo . V C99, nové klíčové slovo, _Boolje představen jako nový booleovský typ. Hlavička stdbool.hposkytuje makra bool, truea falsekteré jsou definovány jako _Bool, 1a 0, v tomto pořadí. Proto truea falsezadejte typ intv C.

Několik dalších rozdílů z předchozí části lze také využít k vytvoření kódu, který se kompiluje v obou jazycích, ale chová se odlišně. Následující funkce například vrátí různé hodnoty v C a 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)
     */
}

Důvodem je, že C vyžaduje structpřed strukturními značkami (a sizeof(T)odkazuje tedy na proměnnou), ale C ++ umožňuje její vynechání (a tedy sizeof(T)odkazuje na implicitní typedef). Dávejte pozor na to, že výsledek je odlišný, když je externdeklarace umístěna uvnitř funkce: pak přítomnost identifikátoru se stejným názvem v rozsahu funkcí zabrání implicitnímu typedefúčinku pro C ++ a výsledek pro C a C ++ by byl stejný. Všimněte si také, že nejednoznačnost ve výše uvedeném příkladu je způsobena použitím závorky s sizeofoperátorem. Použití sizeof Tby očekávalo, Tže bude výrazem, a ne typem, a proto by příklad nebyl kompilován s C ++.

Propojení kódu C a C ++

Zatímco C a C ++ udržují velký stupeň kompatibility zdrojů, soubory objektů, které produkují jejich příslušné kompilátory, mohou mít důležité rozdíly, které se projevují při míchání kódu C a C ++. Zejména:

Z těchto důvodů je pro C ++ kódu volání funkce C foo(), C ++ kód musí prototyp foo() s extern "C". Podobně pro kód C pro volání funkce bar()C ++ bar()musí být kód C ++ pro deklarován pomocí extern "C".

Běžnou praxí, aby soubory záhlaví udržovaly kompatibilitu C i C ++, je provést deklaraci extern "C"pro rozsah záhlaví:

/* Header file foo.h */
#ifdef __cplusplus /* If this is a C++ compiler, use C linkage */
extern "C" {
#endif

/* These functions get C linkage */
void foo();
 
struct bar { /* ... */ };

#ifdef __cplusplus /* If this is a C++ compiler, end C linkage */
}
#endif

Rozdíly mezi C a C ++ propojení a konvence volání může mít i jemné důsledky pro kód, který používá ukazatelů funkcí . Některé kompilátory vytvoří nefunkční kód, pokud ukazatel funkce deklaruje extern "C"body pro funkci C ++, která není deklarována extern "C".

Například následující kód:

void my_function();
extern "C" void foo(void (*fn_ptr)(void));

void bar()
{
   foo(my_function);
}

Pomocí kompilátoru C ++ společnosti Sun Microsystems to vytvoří následující varování:

 $ CC -c test.cc
 "test.cc", line 6: Warning (Anachronism): Formal argument fn_ptr of type
 extern "C" void(*)() in call to foo(extern "C" void(*)()) is being passed
 void(*)().

To je proto, že my_function()není deklarována s C vazbou a volací konvence, ale je předán do funkce C foo().

Reference

externí odkazy