C
Un article de Haypo.
Retour aux langages de programmation
Cet article est en cours de rédaction. Sa qualité est pauvre et son intérêt limité. Revenez un peu plus tard (ça peut être long), et lisez un autre article en attendant ;-) Si vous êtes impatient de lire la suite, secouez un peu son auteur :o)
« Il est dit que les programmeurs Lisp savent que
la gestion de la mémoire est si importante
qu'elle ne peut être laissée aux programmeurs,
et que les programmeurs C savent que
la gestion de la mémoire est si importante
qu'elle ne peut être laissée au système » — Bjarne Stroustrup
Le langage C, c'est de l'assembleur avec des fleurs tout autour.
Sommaire |
Notes
- inline : demande au compilateur d'inliner (copier le contenu d') une fonction (si possible ?)... Je pense qu'un compilateur intelligent doit être capable de le faire lui-même
- register : demande au compilateur d'utiliser un registre pour stocker une variable (si possible ?)
- static : modifie la portée d'une fonction, elle ne sera vue que dans le fichier courant
- extern : indique que la variable ou la fonction déclarée et en fait instanciée dans un autre fichier
- typeof(x) donne le type d'une variable. La macro suivante vérifie que X et Y sont de même type :
#define min(X, Y) \ ({ typeof (X) x_ = (X); \ typeof (Y) y_ = (Y); \ (x_ < y_) ? x_ : y_; })
- Macro pitfalls (CPP)
- gcc -Wall -Wextra -Werror (-Wformat)
- Hardening
Typage dangereux
Le C est un langage dangereux dans la déclaration des types de donnée et des conversions.
Problèmes :
- on ne sait pas si le type char est signé ou non
- la taille en bits d'un type (short, int, long) dépend du compilateur et de l'architecture
- beaucoup de conversions dangereuses sont passées sous silence :
- Conversion int <=> pointeur (void*) : pose des problèmes quand les deux types n'ont pas la même taille (ce qui arrive sur des processeurs 64-bit)
- Mélange entier signé et entier non signé avec conversion silencieuse
- Passage d'un entier signé à un entier signé plus grand
Illustration :
$ cat a.c #include <stdio.h> int main() { char c = 250; unsigned int x = c; printf("c %%u=%u\n", c); printf("c %%i=%i\n", c); printf("x %%u=%u\n", x); return 0; } $ gcc a.c -o a -Wall -Wextra && ./a c %u=4294967290 c %i=-6 x %u=4294967290
On copie 250 dans x et on obtient 4294967290. C'est plutôt gênant... Notez que gcc (4.1.2) n'affiche aucun avertissement.
Pointeurs dangereux
Autre danger en C : les pointeurs. Le langage autorise de lire et écrire n'importe où en mémoire. Or dans un système d'exploitation offrant une protection de la mémoire (utilisant une MMU), les accès illégaux génèrent un signal SIGSEGV qui est quasiment toujours fatal.
Pourtant, il est difficile voir impossible de se passer des pointeurs en C. Les langages Java et Python cachent les pointeurs mais ils existent toujours. Par contre, en Python il est impossible de faire un accès illégal en mémoire. Il sera sanctionné par une exception (LookupError sur un tableau, un dictionnaire ou une chaîne).
Au mieux, on peut vérifier les accès mémoire avec le programme Valgrind. Mais on ne pourra jamais garantir à 100% que tous les accès sont valides car on n'a pas de moyen formel de vérifier tous les cas d'utilisation d'un programme.
L'erreur la plus courante en C est le déreferrencement d'un pointeur nul. Exemple abrégé : « int *x = 0; printf("x=%i\n", *x); ». Il set souvent du au fait que la fonction malloc() peut échouer et que son code de retour n'est pas vérifie. Il vaut mieux utiliser une fonction plus haut niveau qui va générer une erreur dans le cas où malloc() échoue (retourne NULL). La fonction g_new() de la glib stoppe le programme avec abort() par exemple.
stdint
#include <stdint.h>
La bibliothèque stdint.h propose de manière standards les types entiers de 8, 16, 32 et 64 bits.
Type contenant exactement XX bits :
- int8_t / uint8_t
- int16_t / uint16_t
- int32_t / uint32_t
- int64_t / uint64_t
Types ayant "au moins XX bits" :
- int_least8_t / uint_least8_t
- int_least16_t / uint_least16_t
- int_least32_t / uint_least32_t
- int_least64_t / uint_least64_t
Type "rapide" :
- int_fast8_t / uint_fast8_t
- int_fast16_t / uint_fast16_t
- int_fast32_t / uint_fast32_t
- int_fast64_t / uint_fast64_t
Autres types :
- intptr_t / uintptr_t (pour les pointeurs « void * »)
- intmax_t / uintmax_t (nombre entier maximum)
Macro pour indiquer une valeur entière immédiate à la manière de « 1UL » qui équivaut à « (unsigned long)1 » :
- INT8_C(x) / UINT8_C(x)
- INT16_C(x) / UINT16_C(x)
- INT32_C(x) / UINT32_C(x)
- INT64_C(x) / UINT64_C(x)
- INTMAX_C(x) / UINTMAX_C(x)
Valeurs maximales :
- INT8_MIN / UINT8_MIN
- INT16_MIN / UINT16_MIN
- INT32_MIN / UINT32_MIN
- INT64_MIN / UINT64_MIN
- INTMAX_MIN / UINTMAX_MIN
- INTPTR_MIN / UINTPTR_MIN et PTRDIFF_MIN / PTRDIFF_MAX
- SIG_ATOMIC_MIN / SIG_ATOMIC_MAX (limites de « sig_atomic_t »)
- Et il existe les même pour LEAST et FAST : INT_LEAST8_MIN, ..., INT_FAST8_MIN, ...
inttypes
#include <inttypes.h>
La bibliothèque inttypes.h propose des macros pour utiliser printf avec les types définis par stdint.h.
Il existe une macro pour chaque type stdint et chaque type printf. La convention de nommage est « "PRI", type printf, type stdint (signé ou non) ». Exemples :
- PRId32 est utilisé pour faire un printf "%i" avec un type int32_t ou uint32_t
- PRIu64 est utilisé pour faire un printf "%u" avec un type int64_t ou uint64_t
- PRIXFAST16 est utilisé pour faire un printf "%X" avec un type int_fast16_t ou uint_fast16_t
De manière similaire, il existe pour scanf utilisant la convention de nommage est « "SCN", type scanf, type stdint ». Exemples :
- SCNd8 : scanf "%d" pour le type int8_t ou uint8_t
- SCNx32 : scanf "%x" pour le type int32_t ou uint32_t
En pratique, il faut ajouter le pourcent :
printf("x=%" PRId32, x); scanf("%" SCNd32, &x);
Types standards
- size_t : unsigned int... ou unsigned long ?
Articles connexes
Liens externes
- D. J. Bernstein (http://cr.yp.to) : Site web d'un excellent programmeur C. Ses programmes sont réputés pour être sûr (dans le sens sécurité)
- IOCCC : The International Obfuscated C Code Contest, concours du code source le plus illisible :-)
- The Underhanded C Contest
- Rien à voir, Just another Perl hacker
- Les nouveautés du C99