C

Un article de Haypo.

Retour à la page précédente Retour aux langages de programmation

Avertissement

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

[modifier] 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.

size_t vs uintptr_t.

[modifier] Fonctions GCC : __builtin_types_compatible_p, typeof

[modifier] 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;".

[modifier] 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");

[modifier] Integer overflow

[modifier] a + b (unsigned)

Méthode propre :

unsigned int a, b, c;
if (a > UINT_MAX - b) /* integer overflow */
c = a + b;

[modifier] 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.

[modifier] 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.

[modifier] Exemples

[modifier] define

[modifier] 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

[modifier] 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"

[modifier] 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

[modifier] Divers

  • Endian :
    • __LITTLE_ENDIAN
    • __BIG_ENDIAN
    • __BYTE_ORDER
  • Convention d'appel de fonction :
    • __CDECL__ : Convention C
    • __PASCAL__ : Pascal

[modifier] Précompilateur

  • Tests :
    • #if test
    • #if defined(...) || defined(...)
    • #if X == Y
    • #ifdef
    • #elif
    • #else
    • #endif
  • #error
  • #define macro(arguments) ...code de la macro...

Sources :

[modifier] 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_; })

[modifier] 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.

[modifier] 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.

[modifier] 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, ...

[modifier] 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);

[modifier] 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

[modifier] Articles connexes

[modifier] Liens externes

Récupérée de « http://haypo.alwaysdata.net/wiki/C »