C
Un article de Haypo.
Version du 3 février 2012 à 00:31 (modifier) Haypo (Discuter | Contributions) (→Taille des types) ← Différence précédente |
Version du 15 août 2014 à 22:33 (modifier) (défaire) Haypo (Discuter | Contributions) (→Taille des types) Différence suivante → |
||
Ligne 11 : | Ligne 11 : | ||
== Taille des types == | == Taille des types == | ||
- | char < short < int ≤ long ≤ size_t ≤ void * ≤ time_t ≤ off_t ≤ long long | + | char < short < int ≤ long ≤ size_t ≤ void * ≤ intptr_t = uintptr_t = ptrdiff_t ≤ time_t ≤ off_t ≤ long long |
Exemples (bits) : | Exemples (bits) : |
Version du 15 août 2014 à 22:33
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 |
Taille des types
char < short < int ≤ long ≤ size_t ≤ void * ≤ intptr_t = uintptr_t = ptrdiff_t ≤ time_t ≤ off_t ≤ long long
Exemples (bits) :
- char=8, short=16, int=32
- Linux 32 bits :
- 32 : int, long, void*, size_t, time_t, uintptr_t
- 64 : long long, off_t
- Windows 32 bits :
- 32 bits : int, long, clock_t, size_t, void*, uintptr_t, off_t
- 64 bits : time_t, LONGLONG
- Windows 64 bits :
- 32 : int, long
- 64 : void*
- Linux 64 bits :
- 32 : int
- 64 : long, void*, size_t, long long, time_t, uintptr_t, off_t
Note : Sur les OS 16 bits, int fait 16 bits au lieu de 32.
Fonctions GCC : __builtin_types_compatible_p, typeof
array
Tester si une variable est un tableau constant :
!__builtin_types_compatible_p(typeof(array), typeof(&(array)[0]))))
Permet de distinguer "int array[10];" de "int *not_array;".
chaîne constante
Tester si une variable est une chaîne constante ("immédiate") :
#define is_const(str) __builtin_constant_p(str)
Ce qui donne vrai pour "abc", faux pour les variables suivantes :
char *heap = strdup("heap"); const char *heap_ptr_copy = heap; const char *char_p = "char*"; const char char_array[] = "char[]"; char *alloca_var = alloca(sizeof("alloca")); strcpy(alloca_var, "alloca");
Integer overflow
a + b (unsigned)
Méthode propre :
unsigned int a, b, c; if (a > UINT_MAX - b) /* integer overflow */ c = a + b;
a * b (unsigned)
Méthode propre :
unsigned int a, b, c; if (b != 0 && a > UINT_MAX / b) /* integer overflow */ c = a * b;
Alternative :
unsigned int a, b, c; c = a * b; if (b != 0 && c / b != a) /* integer overflow */
Bien sûr, ces tests peuvent être simplifiés s'il est garanti que b n'est jamais nul.
a * b (signed, a et b positifs)
Méthode propre :
int a, b, c; assert(a >= 0 && b >= 0); if (b != 0 && a > INT_MAX / b) /* integer overflow */ c = a * b;
Alternative :
int a, b, c; assert(a >= 0 && b >= 0); c = a * b; if (b != 0 && (c < 0 || c / b != a)) /* integer overflow */
Bien sûr, ces tests peuvent être simplifiés s'il est garanti que b n'est jamais nul.
Exemples
- CVE-2007-4965 python: Integer overflow in imageop module: commit
- CVE-2008-2316: python: integer overflow in hashlib module: commit, rapport de bug RedHat
- CVE-2008-2315, CVE-2008-2316: dev-lang/python <2.4.4-r14 integer overflows
- CVE-2006-7228
- audioop: incorrect integer overflow checks: commit 1, commit 2
define
Compilateur
- __cplusplus ou __cplusplus__: défini pour un compilateur C++
- __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__ : Version majeure, mineure, révision de GCC
- Borland :
- __BORLANDC__ : Version de Borland C
- __BCPLUSPLUS__ : Version de Borland C++
- __BCPLUSPLUS__ : Version de Borland C++ Builder
- __TURBOC__ : Turbo C
- _MSC_VER : Microsoft Visual Studio
- __INTEL_COMPILER, __ICC : ICC
Fichier source
- __DATE__ : Date de compilation
- __TIME__ : Heure de compilation
- __FILE__ : Fichier courant
- __LINE__ : Numéro de la ligne courante
- __func__ : Nom de la fonction courante (non standard). Fait parti de la norme ISO C99 (et également parti de la norme C++ ?)
- __PRETTY_FUNCTION__ : Nom de la fonction courante, type de retour et type des arguments (non standard). Exemple : "void a::sub(int)"
- __FUNCTION__ : Nom de la fonction courante (non standard)
- __FUNCDNAME__ : Nom "Adorned" de la fonction. Visual Studio 2005 uniquement. Exemple : "?myfunc@@YAXXZ"
- __FUNCSIG__ : Signature de la fonction. Visual Studio 2005 uniquement. Exemple : "void __thiscall S::myfunc(void) const"
Système d'exploitation
- BSD : FreeBSD, NetBSD, OpenBSD
- __FreeBSD__, __FreeBSD : FreeBSD
- __NetBSD__, __NetBSD : NetBSD
- __OpenBSD__, __OPENBSD : OpenBSD
- linux, __linux, __linux__ : Linux
- __CYGWIN__ : Cygwin
- __MINGW32__ : MinGW
- _Windows, _WIN32, __WIN32__ : Windows
- __CONSOLE__ : Programme Windows en mode console
- __APPLE__ : Mac OS
- __MSDOS__ : MS Dos
- __TINY__, __SMALL__, __COMPACT__, __MEDIUM__, __LARGE__, __HUGE__ : Modèle mémoire
- __OVERLAY__ : active l'overlay
- sun, __sun: SunOS, Solaris
- _AIX : AIX
Divers
- Endian :
- __LITTLE_ENDIAN
- __BIG_ENDIAN
- __BYTE_ORDER
- Convention d'appel de fonction :
- __CDECL__ : Convention C
- __PASCAL__ : Pascal
Précompilateur
- Tests :
- #if test
- #if defined(...) || defined(...)
- #if X == Y
- #ifdef
- #elif
- #else
- #endif
- #error
- #define macro(arguments) ...code de la macro...
Sources :
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
Fichiers où sont définis les types standards :
- stddef.h
- Types : ptrdiff_t, size_t, ssize_t, wchar_t, wint_t
- Linux : /usr/lib/gcc/*/*/include/stddef.h
Types :
- size_t : unsigned long
Articles connexes
Liens externes
- CCAN : Bibliothèque de fonctions C sous licence libre (BSD, LGPL, GPL, ...)
- 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