Dnes probereme další drobné rozdíly mezi C a C++, napíšeme program který odpovídá normám
obou jazyků, ale v každém dělá něco jiného. Povíme si také, proč je třeba někdy kombinovat
C s C++, jaké problémy při tom vznikají a jak se dají jednoduše vyřešit.
1.2.2006 06:00 | Jan Němec | přečteno 33095×
Minule jsme si ukazovali, že C++ není jen rozšířením jazyka C, ale že má také některá omezení. Situace je ovšem ještě o něco komplikovanější. S trochou znalostí můžeme napsat kód, který vyhovuje normám obou jazyků, ale v každém z nich dělá něco jiného.
Především C++ definuje makro __cplusplus, pomocí podmíněného překladu tak můžeme napsat dvojí kód. Později si ukážeme, že se toho hodně využívá v knihovnách s rozhraním pro oba jazyky se společnými hlavičkovými soubory. Právě #ifdef __cplusplus je ten správný postup, pokud potřebujeme detekovat C++.
V C mají znakové konstanty typ int, v C++ char.
Již z minula víme, že v C mají struktury vlastní prostor jmen a v C++ nikoli. Díky překrývání globálních identifikátorů lokálními a operátoru sizeof tak lze vykouzlit kód, který v obou jazycích dělá něco jiného.
Níže uvedený kód berte jako odstrašující případ, nikoli jako návod pro vlastní programování.
#include <stdio.h> int x; int main(void) { struct x { int i, j; }; /* V C++ je definováno makro __cplusplus. */ puts("Test 1"); #ifdef __cplusplus puts("C++"); #else puts("C"); #endif /* V C++ je znaková konstanta char, v C int. */ puts("Test 2"); if (sizeof('A') == sizeof(char)) { puts("C++"); } else { puts("C"); } /* V C++ nemají struktury vlastní prostor jmen, lokální definice struktury tak zastíní globální definici proměnné se stejným názvem. */ puts("Test 3"); if (sizeof(x) != sizeof(int)) { puts("C++"); } else { puts("C"); } return 0; }
Mezi oběma jazyky existují další drobné odlišnosti, které mohou programátora potrápit.
Zápis funkce s prázdnými závorkami v hlavičce znamená v C funkci, o jejíchž parametrech nic nevíme. V C++ jde o funkci bez parametrů. Nejlepší je asi psát v obou jazycích
void funkce(void)
v tomto případě jde vždy o funkci bez parametrů.
C++ zná a rozlišuje 3 různé typu odvozené od char: char, unsigned char a signed char. Později si ukážeme, že je to důležité například pro přetížené funkce.
V C++ je typ bool, který je určen pro logické hodnoty. Tento typ také vrací příslušné operátory (&&, || a podobně), zatímco v C je návratovým typem int.
C sice zná modifikátor const, ale například
const int sedm = 7;
je ve skutečnosti jen definice proměnné, která má své umístění v paměti, pouze nelze její hodnotu standardními prostředky měnit. V C++ se jedná opravdu o konstantu, která nemusí být linkována a může dokonce nahradit symbolické konstanty preprocesoru například při definici mezí polí a podobně.
const int sedm = 7;
int pole[sedm];
Jak již víme, v C i C++ se jednotlivé zdrojové soubory překládají na objektové (zpravidla mají jméno *.o nebo *.obj na Windows). Spustitelný program se slinkuje z přeloženého kódu v objektových souborech a statických knihovnách. V C jednoznačně identifikuje funkci (která není static) nebo globální proměnnou na úrovni přeloženého kódu její jméno z kódu zdrojového. Bohužel v C++ to takhle jednoduše nejde. Díky přetěžování funkcí, metodám tříd a prostorům jmen může existovat ve zdrojovém kódu více funkcí se stejným identifikátorem. V přeloženém kódu je ovšem duplicita nepřípustná, překladač proto musí název rozšířit o nějaký řetězec odvozený od prostoru jmen, případné třídy a typu parametrů.
Zkuste přeložit (ale nelinkovat) následující kód
void funkce(void) {} void funkce(int i, const char *str) {}
jako C++
g++ priklad.cpp -c -o cpp.o
a potom jako C.
gcc priklad.c -c -o c.o
V případě C to udělejte dvakrát a vždy jednu z funkcí zakomentujte, jinak dojde ke konfliktu identifikátorů.
Podívejte se nějakým textovým prohlížečem, úplně stačí vim, na soubory c.o (obě verze) a cpp.o. V případě C naleznete v obou případech na konci souboru symbol funkce, zatímco C++ generuje názvy jako _Z6funkcev a _Z6funkceiPKc. Zakomponování typu parametrů je docela názorné.
Z uvedených rozdílů v přeloženém kódu vyplývají určité komplikace při spojování modulů napsaných v C s těmi v C++. Jde přitom o běžnou situaci, například každá linuxová distribuce obsahuje obrovské množství přeložených knihoven, statických i dynamických, řada z nich je určena pro oba jazyky a až na výjimky existují v jediné společné variantě tj. nikoli přeložená zvlášť pro C a zvlášť pro C++. Programátor, který je používá se přitom (obvykle) nemusí o nic starat. Jak je to možné?
Kdyby se ani programátor knihovny v C o nic nestaral a aplikační programátor v C++ použil funkci z knihovny, skončí překlad programu chybou linkeru, který by například místo symbolu funkce hledal v knihovně třeba symbol _Z6funkceiPKc. Naštěstí jazyk C++ obsahuje modifikátor funkce, který změní její linkování ve stylu C
extern "C" void funkce(int i, const char *str) {
/* ... */
}
nebo alternativně pro více funkcí najednou.
extern "C" { void funkce1(int i, const char *str) { /* ... */ } void funkce2(int i, const char *str) { /* ... */ } /* ... */ }
Totéž se uvádí i v hlavičkovém souboru knihovny.
extern "C" void funkce(int i, const char *str);
Pokud tedy chceme nějakou knihovnu používat z obou jazyků, používáme standard volání funkcí ve stylu C. Věc je však ještě o trochu komplikovanější, neboť konstrukci extern "C" lze přeložit pouze v C++, takže například v hlavičkovém souboru knihovny je třeba použít podmíněný překlad.
Ukážeme si to na příkladu, napíšeme si knihovnu a program, který ji využívá. Nebude přitom záležet na jazyku programu a navíc ani na jazyku knihovny, všechny 4 kombinace C a C++ budou fungovat.
Knihovna poskytuje jedinou funkci, která vrací název jazyka, v němž je přeložená. Zdrojový kód tvoří soubory knihovna.h a knihovna.c respektive .cpp. Začneme hlavičkovým souborem.
/* knihovna.h */ #ifndef knihovnaH #define knihovnaH /* Makro CFUNKCE se rozvine na extern "C" v C++ nebo na prázdný řetězec v C. */ #ifdef __cplusplus #define CFUNKCE extern "C" #else #define CFUNKCE #endif CFUNKCE const char * jazykKnihovny(void); #endif
Implementace funkce jazykKnihovny je jednoduchá.
/* knihovna.c nebo .cpp */ #include "knihovna.h" CFUNKCE const char * jazykKnihovny(void) { #ifdef __cplusplus return "C++"; #else return "C"; #endif }
Program jen vypíše svůj jazyk a pomocí funkce jazykKnihovny rovněž jazyk, kterým byla přeložena knihovna.
/*program.c nebo .cpp */ #include <stdio.h> #include "knihovna.h" const char * jazykProgramu(void) { #ifdef __cplusplus return "C++"; #else return "C"; #endif } int main(void) { printf("Kód v %s volá funkci knihovny v %s.\n", jazykProgramu(), jazykKnihovny()); return 0; }
Všimněte si, že hlavní program je velmi jednoduchý a že aplikační programátor ani nemusí znát rozdíly v linkování mezi C a C++, konstrukci extern "C" a nemusí rovněž vědět, v jakém jazyku je knihovna implementována a jakým způsobem se linkují funkce rozhraní knihovny.
Na překlad a spuštění všech čtyř variant bude asi nejlepší si napsat malý skript.
#/bin/sh gcc knihovna.c -c gcc program.c -c gcc program.o knihovna.o -o program ./program gcc knihovna.c -c g++ program.cpp -c g++ program.o knihovna.o -o program ./program g++ knihovna.cpp -c gcc program.c -c g++ program.o knihovna.o -o program ./program g++ knihovna.cpp -c g++ program.cpp -c g++ program.o knihovna.o -o program ./program
Zbývá už jen se pokochat výsledkem:
[honza@localhost]$ chmod 744 skript [honza@localhost]$ ./skript Kód v C volá funkci knihovny v C. Kód v C++ volá funkci knihovny v C. Kód v C volá funkci knihovny v C++. Kód v C++ volá funkci knihovny v C++. [honza@localhost]$
Drobných rozdílů mezi C a C++ jsme si už užili víc než dost. Od příštího dílu se budeme zabývat tím, co je v C++ navíc.