C Cpp

Le C++ s’est vu comme une surcouche du C, mais dès le début des différences existent et un code valide en ANSI C peut ne pas compiler en C++.

Incompatibilités depuis l’ANSI C

Appeller la fonction main récursivement

En C on peut appeller la fonction main(), pas en C++

 int point_d_entree_alternatif_du_programme() {
    // ...

    main(); // illegal en C++
 }

Accéder à une variable déclarée dans une instruction for

Le C ANSI ne permet pas de déclarer une variable dans une boucle for , mais cela a été possible de-facto par gcc avant le C99. La durée de vie de cette variable est automatique mais sa portée est visible dans le bloc où elle est déclarée.

Code avant l’Ansi C

#include <stdio.h>

/* Avant l'ANSI C */
int main() {
    int i;
    int j = 23;

    for(i = 0; i < 3 ; i++) {
        printf("%d", i);  // 0, 1, 2
    }

    printf("%d", i);  // 3
}

Depuis l’Ansi C (GCC et clang, ne compile pas avec MSVC)

#include <stdio.h>

// Depuis l'ANSI C
int main() {
    // illégal en K&R C, toléré de facto en C Ansi, avec règles de
    for(int i = 0; i < 3 ; i++) {  
        printf("%d", i);
    }

    printf("%d", i); // légal en C ansi, illégal en C99 et C++

    // légal en C99, illégal en C ansi, 
    // considéré comme une redéclaration
    for(int i = 0; i < 3 ; i++) {  
        printf("%d", i); 
    }

    {
        int j = 23;  // portee limitée au bloc
        // on peut déclarer les variables
        // ailleurs qu'en tête de fonction
        printf("%d\n", j);  // 23

        // legal en C gcc, la portee de i est limitée au bloc
        for(int i = 0; i < 3 ; i++) {  
            printf("%d", i); 
        }
    }

    // printf("%d\n", j);  // illégal dans tous les cas
}

Le C ansi permet de facto de déclarer une variable au sein d’une boucle for, mais la portée de celle ci est celle de la fonction :

#include <stdio.h>

main() {
	printf("test\n");

	for(int i = 0; i < 3 ; i++) {
		printf("%d\n", i);
	}
	printf("%d\n", i);
}

// gcc -std=c89 ./test.c && ./a.out

En compilant en mode ANSI le programme affiche

test
0
1
2
3

Depuis le C99, une variable déclarée dans une instruction for a une portée limitée à l’instruction. En C++ la portée d’une telle variable est étendue au bloc contrôlé par l’instruction.

#include <stdio.h>

int main() {
	printf("test\n");

	for(int i = 0; i < 3 ; i++) {
		int i = 42;     // illégal en C++
	}
}

Zero initialisation de structures avec enumerations

En C, admettons que nous ayons une structure de la sorte


struct toto {
    enum tata {
        TATA, TOTO, TITI
    } state;
    int length;
    float time;
}

On pourra la “zero initialiser” de la sorte en C



int main() {

    struct toto toto = { 0 };
}

En C++, l’enumeration en premiere position l’empeche et il faut ecrire


int main() {
    toto toto = { TATA }; 
    // le reste de la structure est zero initialisee
}

Mais si on declare la structure en ne mettant pas d’enumération en première position, on pourra zero initialiser la structure en C et C++.

struct toto {
    int _placeholder_do not_use;
    enum tata {
        TATA, TOTO, TITI
    } state;
    int length;
    float time;
}

int main() {

    struct toto toto = { 0 }; // fonctionne en C et C++
    // le mot clé struct est optionel en C++
}

Incompatibilités depuis le C99

Très peu de nouveautés du C99 sont utilisables en C++

VLA, pointer-to-vla, flexible length arrays, restrict, specificateurs static de pointeurs, compound statements, designated initialisers.

Rendre un programme compatible en C et C++

Pour qu’un programme puisse compiler en C/C++ il faut généralement l’écrire en C, en évitant certaines constructions incompatibles évoquées dans cette page, notammenent il faut cast le type de retour de malloc même si le type de variable cible permet de le deviner.

#include <stdlib.h>

int main() {
    int * toto = malloc(sizeof *toto); // ne fonctionne qu'en C
    int * tata = (int*)malloc(sizeof *tata); // ok en C/C++

}

Il faut également spécifier au compilateur C++ un linkage de type C pour les identifiants globaux externes.

// header.h

extern "C" int toto();

// ou encore

extern "C" {

    int tata();
}

Un compilateur C ne reconnait pas la construction en bloc d’extern ni le mot clé "C", on ajoute donc une définition de préprocesseur ainsi pour signifier que ca ne servira que à un compilateur C++.

// header.h
#ifdef __cplusplus
extern "C" {
#endif

int toto();
int tata();

#ifdef __cplusplus
} // #ifdef __cplusplus
#endif

En se limitant au C ansi (avec quelque précautions), castant le type de retour de malloc et en déclarant les identificateurs externes avec la convention du C, on arrive à écrire un programme « C/C++ » qui fonctionne avec les deux langages. Ce dialecte est surtout utile pour les librairies utilitaires.

Apports du C++ dans l’ANSI C

Fontions a prototypes et eumerations.

Apports du C++ dans le C23

Les attributes, variables auto, les lambdas

Voila ce qu’il faut changer dans un programme pour qu’il compile a la fois en C et en C++.

Les fonctionalités du C adoptées par le C++17 (structures initialisée designées).

Convergence prévue pour le C/C++23

Fonctionalités du C++ dans le C (fonctions a prototypes, enums, constexpr)