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 | read 33366×
DISCUSSION
Drobné nekompatibility
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.
Makro __cplusplus
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++.
Znaková konstanta
V C mají znakové konstanty typ int, v C++ char.
Prostor jmen struktur
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.
void funkce()
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
v tomto případě jde vždy o funkci bez parametrů.
Různé typy char
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.
Typ bool
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.
Konstanty
C sice zná modifikátor const, ale například
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];
Odlišnosti při linkování
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é.
Linkování C s C++
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
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
}
Hlavní program
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]$
Pokračování příště
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.