C/C++ (33) - Rozdíly mezi C a C++

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 32661×

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

void funkce(void)

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

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];

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.

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=1087