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ímalloc
alokace 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ř.strchr
Vrací sechar*
v C, zatímco C ++ funguje, jako by existovaly dvě přetížené funkceconst char *strchr(const char *)
achar *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é konstanty
enum
výčtu ( enumerátory) jsou vždy typuint
v C, zatímco v C ++ jsou odlišné typy a mohou mít jinou velikost než ta ze dneint
. - V C ++
const
musí 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
,union
neboenum
je platný, ale je neplatná v jazyce C ++, neboť v Cstruct
,union
aenum
druhy 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 vint 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é
struct
typy, ale rozsah je interpretován odlišně: v C ++ je vnořenýstruct
definován pouze v rámci oboru/oboru názvů vnějšíhostruct
, zatímco v C je vnitřní struktura definována také mimo vnější strukturu. - C umožňuje
struct
,union
aenum
typy, 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 complex
adouble complex
primitivní datové typy byla přidána do standardu C99 prostřednictvím makra_Complex
klíčových slov acomplex
praktič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 typuint
v jazyce C a typuchar
v jazyce C ++, což znamená, žesizeof 'a'
v obou jazycích budou obecně poskytovány různé výsledky: v jazyce C ++ bude1
, zatímco v jazyce C budesizeof(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, zdachar
jde o podepsaný nebo nepodepsaný typ, zatímco pro C ++ je to specifické pro implementaci kompilátoru. - C ++ přiřazuje interní propojení
const
proměnným s rozsahem oboru názvů, pokud nejsou výslovně deklaroványextern
, na rozdíl od C, ve kterémextern
je 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
inline
funkcí : 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 konstantamitrue
afalse
, ale jsou definovány odlišně. V C ++bool
je vestavěný typ a vyhrazené klíčové slovo . V C99, nové klíčové slovo,_Bool
je představen jako nový booleovský typ. Hlavičkastdbool.h
poskytuje makrabool
,true
afalse
které jsou definovány jako_Bool
,1
a0
, v tomto pořadí. Prototrue
afalse
zadejte typint
v 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 struct
př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 extern
deklarace 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 sizeof
operátorem. Použití sizeof T
by 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:
- Kompilátory jazyka C nepojmenovávají symboly manglování způsobem, jakým to dělají kompilátory jazyka C ++.
- V závislosti na kompilátoru a architektuře se také může stát, že se konvence volání mezi těmito dvěma jazyky liší.
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
- Podrobné srovnání , věta po větě, z pohledu C89 Standard.
- Inkompatibility mezi ISO C a ISO C ++ , David R. Tribble (srpen 2001).
- Průvodce migrací Oracle (Sun Microsystems) C ++, část 3.11 , Dokumenty kompilátoru Oracle/Sun o rozsahu propojení.
- Oracle: Míchání kódu C a C ++ ve stejném programu , přehled Steve Clamage (předseda výboru ANSI C ++).