grim7reaper

Un artisan du code

Les déclarations style K&R c’est la misère

Cet article provient de mon ancien site Internet.

C’est quoi ?

C’est une manière de déclarer les fonctions. Les déclarations K&R sont un artefact qui date de l’époque où le C n’était pas standardisé (mais qui est encore valide aujourd’hui pour cause de compatibilité). On parle de C pré‑ANSI ou de C K&R. Nombre d’entre vous connaissent cette syntaxe :

C standard
1
2
3
4
int main(int argc, char** argv)
{
    /* Do something. */
}

Et bien en style K&R, on peut faire ça :

C K&R
1
2
3
4
int main(argc, argv) int argc; char** argv;
{
    /* Do something. */
}

Oui c’est moche, mais le pire est à venir…

Pourquoi c’est mal ?

Comme un bout de code vaut mieux qu’un long discours.

1
2
3
4
5
6
7
8
9
10
11
12
void foo(bar, baz, qux) int bar; char baz; int* qux;
{
    /* Do something. */
}


int main(void)
{
    foo(42, "foobarbazqux", 1024.8);

    return 0;
}

Et oui, ce truc passe sans aucun souci et sans aucun avertissement du compilateur (je lui passe quand même un flottant en tant qu’adresse et une chaîne de caractères en tant que caractère !!!). Oui, vous avez bien lu, même un gcc -ansi -pedantic -Wall -Wextra laisse passer ça sans broncher. Pour les utilisateurs de clang, j’en ai autant à leur service clang -ansi -pedantic ne l’ouvre pas plus que gcc1. Pire, je n’ai même pas réussi à le faire couiner là-dessus (mais ça vient peut-être aussi de moi, je suis novice en ce qui concerne clang), alors que gcc avec un ‑Wstrict‑prototypes ou un ‑Wold‑style‑definition vous avertit comme il se doit.

Si on essaye le même code avec une déclaration comme on les aime :

1
2
3
4
5
6
7
8
9
10
11
12
void foo(int bar, char baz, int* qux)
{
    /* Do something. */
}


int main(void)
{
    foo(42, "foobarbazqux", 1024.8);

    return 0;
}

Là, le compilateur nous jette (avec des erreurs, pas seulement des avertissements) sans ajouter une seule option (même gcc le laxiste ne laisse pas passer une adresse flottante ^^).

Mais c’est quoi la différence ?

Certains connaissent sûrement déjà cette notation, mais combien savent qu’il y a une grande différence entre les deux ?

La norme (ISO/IEC 9899:TC3, 6.9.1 Function definitions, page 155) nous dit :

extern int max(int a, int b)
{
    return a > b ? a : b;
}
[…]
extern int max(a, b)
int a, b;
{
    return a > b ? a : b;
}
  

Here int a, b; is the declaration list for the parameters. The difference between these two definitions is that the first form acts as a prototype declaration that forces conversion of the arguments of subsequent calls to the function, whereas the second form does not.

Ce qui signifie que les déclarations style K&R ne force pas la conversion, quel que soit les types en jeu. Et c’est ce comportement qui empêche de détecter les erreurs à la compilation.

Pourquoi j’en parle ?

Bah oui tiens, pourquoi j’en parle ? Après tout c’est vieux ce truc, cela date d’avant le C89 donc on s’en fiche, on ne le verra plus. Si seulement…

Il y a encore des gens qui utilisent ce style de déclaration (malgré ses défauts évidents) donc vous risquez de le croiser tôt ou tard. Alors, autant savoir ce que ça signifie et à quel point c’est foireux également (des fois que vous puissiez modifier le code en question, ça pourrait être une bonne chose à faire).

Bon, je conclurai en collant un gros carton rouge à FreeBSD qui a des trucs dans ce genre dans son code. No comment…


  1. Maintenant (2013/09/01), même sans options, clang émet un warning (pour la chaîne de caractères en tant que char) et une erreur (pour le float en tant qu’adresse). gcc ne signale toujours rien si l’on n’utilise pas explicitement les bonnes options. De manière générale, clang (et LLVM) a fait de gros progrès ces dernières années. >↩