Lors de l'examen du code du noyau GNU/Linux, on rencontre une méthode particulière d'initialisation des structures. Cette technique, non abordée dans certains manuels C (comme l'édition II de Tan ou l'édition II de K&R), est connue sous le nom d'initialisation désignée (designated initializer).
Considérons l'exemple suivant tiré de Linux-2.6.x/drivers/usb/storage/usb.c :
static struct usb_driver usb_storage_driver = {
.owner = THIS_MODULE,
.name = "usb-storage",
.probe = storage_probe,
.disconnect = storage_disconnect,
.id_table = storage_usb_ids,
};
Cette syntaxe peut sembler éloignée des méthodes d'initialisation des structures que nous avons apprises précédemment. Il s'agit en fait d'une application des initialisations désignées, conformément à la norme ISO C99, largement utilisée dans les pilotes de périphériques Linux.
L'avantage principal de cette approche, tel qu'expliqué dans C Primer Plus (5e édition), est qu'elle ne nécessite pas de respecter strictement l'ordre de définition des membres. Cette flexibilité offre des avantages considérables que les développeurs peuvent découvrir au fil de leurs expérimentations.
Prenons un exemple concret. Soit la structure suivante :
struct livre {
char titre[MAXTITL];
char auteur[MAXAUTL];
float prix;
};
Le C99 prend en charge l'initialisation désignée des structures, une syntaxe similaire à celle utilisée pour les tableaux. La différence réside dans le fait que l'initialisation désignée des structures utilise l'opérateur point et le nom du membre (plutôt que des crochets et un indice) pour identifier les éléments.
Par exemple, pour initialiser uniquement le membre prix de la structure livre :
struct livre surprise = { .prix = 10.99 };
On peut utiliser les initialisations désignées dans n'importe quel ordre :
struct livre cadeau = { .prix = 25.99, .auteur = "James Broadfool", .titre = "Rue for the Toad"};
Comme avec les tableaux, les initialisations normales qui suivent une initialisation désignée fournissent des valeurs aux membres qui suivent le membre spécifié. De plus, la dernière affectation à un membre est celle qui est effectivement retenue.
Considérons la déclaration suivante :
struct livre cadeau = { .prix = 18.90, .auteur = "Philionna pestle", 0.25};
Cela attribuera la valeur 0.25 au membre prix, car il suit immédiatement le membre auteur dans la déclaration de la structure. La nouvelle valeur 0.25 remplace ainsi l'attribution précédente de 18.90.
Pour des informations complémentaires sur les designated initializers, on peut se référer à la section 6.7.8 du standard C99.
Initialisation spécifique
Le standard C89 exigeait que les éléments d'une instruction d'initialisation apparaissent dans un ordre fixe, correspondant à l'ordre des éléments dans le tableau ou la structure initialisée.
Dans ISO C99, on peut fournir ces éléments dans n'importe quel ordre, en spécifiant l'indice du tableau ou le nom du membre de la structure. GNU C implémente également cette extension en mode C89, bien qu'elle ne soit pas disponible en GNU C++.
Pour spécifier un indice de tableau, on écrit [index] = avant la valeur de l'élément. Par exemple :
int tab[6] = { [4] = 29, [2] = 15 };
Cela est équivalent à :
int tab[6] = { 0, 0, 15, 0, 29, 0 };
L'indice doit être une expression constante, même si le tableau initialisé est automatique.
Une syntaxe alternative consiste à écrire .[index] sans le signe égal, mais elle a été abandonnée depuis GCC 2.5 bien que GCC l'accepte toujours.
Pour initialiser une série d'éléments avec la même valeur, on utilise la syntaxe [premier ... dernier] = valeur. C'est une extension GNU. Par exemple :
int largeurs[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
Si la valeur a des effets de bord, ceux-ci ne se produiront qu'une seule fois, et non pour chaque initialisation dans la plage.
Notez que la longueur du tableau est le maximum spécifié plus un.
Dans les instructions d'initialisation de structure, on spécifie le nom du membre à initialiser en écrivant .nom_membre = avant la valeur de l'élément. Par exemple, étant donné la structure suivante :
struct point {
int x, y;
};
Et l'initialisation suivante :
struct point p = { .y = yvaleur, .x = xvaleur };
Cela est équivalent à :
struct point p = { xvaleur, yvaleur };
Une autre syntaxe avec la même signification est nom_membre:, mais elle a été abandonnée depuis GCC 2.5, comme ceci :
struct point p = { y: yvaleur, x: xvaleur };
[index] ou .nom_membre sont des indicateurs. Lors de l'initialisation d'une union, on peut également utiliser un indicateur (ou la syntaxe obsolète avec deux-points) pour spécifier quel élément de l'union doit être utilisé. Par exemple :
union foo {
int i;
double d;
};
union foo f = { .d = 4 };
Cela utilisera le deuxième élément pour convertir 4 en type double et le stocker dans l'union. Au contraire, convertir 4 en type union foo le stockerait comme entier i dans l'union, puisqu'il s'agit d'un entier.
On peut combiner cette technique de nommage d'éléments avec l'initialisation C normale pour des éléments consécutifs. Chaque élément d'initialisation sans indicateur s'applique à l'élément consécutif suivant dans le tableau ou la structure. Par exemple :
int tab[6] = { [1] = v1, v2, [4] = v4 };
Cela est équivalent à :
int tab[6] = { 0, v1, v2, 0, v4, 0 };
Les indicateurs sont particulièrement utiles lorsque l'indice est un caractère ou de type enum pour spécifier les éléments des instructions d'initialisation de tableau. Par exemple :
int espace_blanc[256] = { [' '] = 1, ['\t'] = 1, ['\h'] = 1,
['\f'] = 1, ['\n'] = 1, ['\r'] = 1 };
On peut également spécifier un objet imbriqué à initialiser en écrivant une série d'indicateurs .nom_membre ou [index] avant le =. Cette liste est relative au sous-objet correspondant à la paire d'accolades la plus proche. Par exemple, avec la déclaration struct point précédente :
struct point tableau_points[10] = { [2].y = yv2, [2].x = xv2, [0].x = xv0 };
Si un membre est initialisé plusieurs fois, il prendra la valeur de la dernière initialisation. Si de telles initialisations de remplacement ont des effets de bord, leur exécution n'est pas spécifiée. Actuellement, gcc les ignore et émet un avertissement.
Plages dans les instructions case
On peut spécifier une série de valeurs consécutives dans une seule instruction case, comme ceci :
case bas ... haut:
Cela a le même effet que des instructions case séparées pour chaque entier compris entre bas et haut.
Cette caractéristique est particulièrement utile pour une série de codes ASCII :
case 'A' ... 'Z':
Attention : entourez les ... d'espaces, sinon ils seront mal interprétés lorsqu'ils sont utilisés avec des valeurs entières. Par exemple, écrivez :
case 1 ... 5:
Et non :
case 1...5:
Conversion vers un type union
La conversion vers un type union est similaire à d'autres conversions, sauf que le type spécifié est un type union. On peut spécifier le type en utilisant le tag de l'union ou un nom de typedef. La conversion vers un union est en fait une construction, pas une véritable conversion, donc contrairement aux conversions normales, elle ne produit pas une lvalue.
Les types pouvant être convertis en type union sont les types des membres de l'union. Donc, étant donné l'union et les variables suivantes :
union foo {
int i;
double d;
};
int x;
double y;
x et y peuvent tous deux être convertis en type union foo.
Convertir vers un union est équivalent à assigner à une variable d'union ou à stocker dans un membre de l'union :
union foo u;
...
u = (union foo) x == u.i = x
u = (union foo) y == u.d = y
On peut également utiliser la conversion d'union comme argument de fonction.
void manipulation (union foo);
...
manipulation ((union foo) x);
Déclarations mélangées et code
ISO C99 et ISO C++ permettent de mélanger librement des déclarations et du code dans des instructions composées. En tant qu'extension, GCC autorise cela également en mode C89. Par exemple, on peut écrire :
int i;
...
i++;
int j = i + 2;
Chaque identifiant est visible depuis son point de déclaration jusqu'à la fin du bloc de contrôle englobant.
Attributs de déclaration de fonction
Dans GNU C, on peut déclarer certaines propriétés sur les fonctions appelées dans votre programme pour aider le compilateur à optimiser les appels de fonction et à vérifier plus attentivement votre code.
Le mot-clé __attribute__ permet de spécifier des attributs particuliers lors de la déclaration. Il est suivi de spécifications d'attributs entre doubles parenthèses. Quatorze attributs noreturn, pure, const, format, format_arg, no_instrument_function, section, constructor, destructor, unused, weak, malloc, alias et no_check_memory_usage sont actuellement définis pour les fonctions. Sur certains systèmes cibles, d'autres attributs sont également définis pour les fonctions. D'autres attributs, y compris section, sont également pris en charge pour les déclarations de variables (voir la section 5.33 Spécifier les attributs de variable) et pour les types (voir la section 5.34 Spécifier les attributs de type).
On peut également entourer chaque mot-clé de __ pour spécifier un attribut. Cela permet de les utiliser dans des fichiers d'en-tête sans craindre une macro ayant le même nom. Par exemple, on peut utiliser __noreturn__ au lieu de noreturn.
Voir la section 5.27 Syntaxe des attributs pour connaître les détails précis de l'utilisation des attributs.
noreturn
Certaines fonctions de la bibliothèque standard, comme abort et exit, ne peuvent pas retourner. GCC le détecte automatiquement. Certains programmes définissent leurs propres fonctions qui ne retournent jamais. On peut les déclarer comme noreturn pour informer le compilateur de ce fait. Par exemple :
void fatale() __attribute__ ((noreturn));
void
fatale(...)
{
... /* Afficher un message d'erreur. */ ...
exit(1);
}
Le mot-clé noreturn indique au compilateur de supposer que fatale ne peut pas retourner. Il peut alors effectuer des optimisations sans se soucier de ce qui se passerait si fatale retournait. Cela produit un code légèrement meilleur. Plus important encore, cela aide à éviter des avertissements erronés sur des variables non initialisées.
Ne supposez pas que les registres sauvegardés par la fonction appelée soient restaurés avant l'appel d'une fonction noreturn.
Il est sans sens pour une fonction noreturn d'avoir un type de retour autre que void.
L'attribut noreturn n'a pas été implémenté dans les versions de GCC antérieures à la 2.5. Une alternative pour déclarer une fonction qui ne retourne pas, qui fonctionne dans la version actuelle et certaines anciennes versions, est la suivante :
typedef void voidfn();
volatile voidfn fatale;
pure
De nombreuses fonctions n'ont d'effet que sur leur valeur de retour, et celle-ci ne dépend que des paramètres et/ou de variables globales. Une telle fonction peut être sujette à l'élimination des sous-expressions communes et aux optimisations de boucle, tout comme un opérateur arithmétique. Ces fonctions devraient être déclarées avec l'attribut pure. Par exemple :
int carre(int) __attribute__ ((pure));
Indique que la fonction supposée carre peut être appelée moins de fois que ce qui est indiqué dans le programme.
Des exemples courants de fonctions pure sont strlen et memcmp. Des fonctions non pure intéressantes sont celles avec une boucle infinie, ou celles qui dépendent de la mémoire volatile ou d'autres ressources système qui peuvent changer entre deux appels consécutifs (comme feof dans un environnement multithread).
L'attribut pure n'a pas été implémenté dans les versions de GCC antérieures à 2.96.
const
De nombreuses fonctions ne vérifient aucune valeur en dehors de leurs paramètres, et n'ont aucun effet en dehors de leur valeur de retour. Essentiellement, c'est légèrement plus strict que l'attribut pure ci-dessus, car la fonction n'est pas autorisée à lire la mémoire globale.
Notez que les fonctions avec des paramètres pointeur qui vérifient les données pointées ne peuvent pas être déclarées const. De même, les fonctions appelant des fonctions non const ne sont généralement pas const non plus. Une fonction const qui retourne void n'a aucun sens.
L'attribut const n'a pas été implémenté dans les versions de GCC antérieures à 2.5. Une alternative pour déclarer qu'une fonction n'a pas d'effets de bord, qui fonctionne dans la version actuelle et certaines anciennes versions, est la suivante :
typedef int intfn();
extern const intfn carre;
Cette méthode ne fonctionne pas en GNU C++ à partir de la version 2.6.0, car le langage indique que const doit s'attacher à la valeur de retour.
Attributs de variable
Le mot-clé __attribute__ permet de spécifier des attributs particuliers pour des variables ou des champs de structure. Ce mot-clé est suivi de spécifications d'attributs entre parenthèses. Huit attributs sont actuellement définis pour les variables : aligned, mode, nocommon, packed, section, transparent_union, unused et weak. D'autres attributs sont définis pour les variables sur certaines machines cibles. D'autres attributs peuvent être utilisés pour les fonctions (voir la section 5.26 Déclarer les attributs de fonction) et pour les types (voir la section 5.34 Spécifier les attributs de type).
On peut également entourer chaque mot-clé de __ pour spécifier un attribut. Cela permet de les utiliser dans des fichiers d'en-tête sans craindre une macro ayant le même nom. Par exemple, on peut utiliser __aligned__ au lieu de aligned.
Voir la section 5.27 Syntaxe des attributs pour connaître les détails précis de l'utilisation des attributs.
alignement
Cet attribut spécifie en octets l'alignement minimal pour une variable ou un champ de structure. Par exemple, cette déclaration :
int x __attribute__ ((aligned (16))) = 0;
Provoque l'allocation de la varible globale x sur une frontière de 16 octets. Sur un 68040, cela peut être utilisé avec une expression assembleur pour accéder à des objets nécessitant un alignement de 16 octets avec l'instruction move16. On peut également spécifier l'alignement des champs de structure. Par exemple, pour créer une paire d'entiers alignée sur un double mot, on peut écrire :
struct foo {
int x[2] __attribute__ ((aligned (8)));
};
C'est une alternative à la création d'une union forcée à être alignée sur un double mot. Il ne peut pas être spécifié pour les fonctions ; l'alignement des fonctions est déterminé par les besoins de la machine et ne peut pas être changé. On ne peut pas spécifier l'alignement d'un nom défini par typedef, car ce nom n'est qu'un alias, pas un type spécifique.
Dans l'exemple précédent, on peut explicitement indiquer que l'on souhaite que le compilateur aligne la variable ou le champ avec un alignement donné (en octets). En alternative, on peut omettre le facteur d'alignement et laisser le compilateur aligner la variable ou le champ avec l'alignement maximal utile pour la machine cible pour laquelle on compile. Par exemple, on peut écrire :
short tableau[3] __attribute__ ((aligned));
Chaque fois que l'on omet le facteur d'alignement dans un attribut aligned, le compilateur définit automatiquement l'alignement de la variable ou du champ déclaré à l'alignement maximal jamais utilisé pour n'importe quel type de données sur la machine cible pour laquelle on compile. Cela peut souvent rendre les copies plus efficaces, car le compilateur peut utiliser n'importe quelle instruction pour copier le plus grand bloc mémoire possible lors de l'exécution d'une copie vers ou depuis une variable ou un champ nécessitant cet alignement.
L'attribut aligned ne peut qu'augmenter l'alignement ; mais on peut également spécifier l'attribut packed pour réduire l'alignement. Voir ci-dessous.
Notez que la faisabilité de l'attribut aligned peut être limitée par les limitations inhérentes de l'éditeur de liens. Sur de nombreux systèmes, l'éditeur de liens ne peut placer et aligner les variables qu'à un alignement maximal spécifique. (Pour certains éditeurs de liens, l'alignement maximal supporté peut être très petit.) Si votre éditeur de liens ne peut aligner les variables qu'à un maximum de 8 octets, spécifier aligned(16) dans __attribute__ fournira toujours un alignement de 8 octets. Consultez la documentation de votre éditeur de liens pour plus d'informations.
mode
Cet attribut dans une déclaration spécifie le type de données — n'importe quel type est acceptable pour le mode mode. Cela vous permet efficacement d'obtenir un entier ou un flottant de la largeur correspondante. On peut également spécifier le mode 'byte' ou 'byte' pour indiquer que le mode correspond à un entier d'un octet, 'word' ou 'word' pour indiquer le mode d'un entier d'un mot, et 'pointer' ou 'pointer' pour indiquer le mode de pointeur.
nocommon
Cet attribut indique que GCC ne doit pas placer une variable en "common" mais lui allouer de l'espace directement. Si l'on spécifie l'option '-fno-common', GCC le fait pour toutes les variables. Spécifier l'attribut nocommon pour une variable fournit une valeur initiale de zéro. La variable ne peut être initialisée que dans un seul fichier source.
packed
L'attribut packed indique qu'une variable ou un champ de structure doit avoir l'alignement le plus petit possible — un octet pour une variable, un octet pour un champ, sauf si l'on spécifie une valeur plus grande avec l'attribut aligned. Voici une structure où le champ x est spécifié avec l'attribut packed, il suit donc directement a :
struct foo {
char a;
int x[2] __attribute__ ((packed));
};
section ("nom-section")
Normalement, le compilateur place les objets dans les sections générées pour eux, comme data et bss. Parfois, cependant, on a besoin de sections supplémentaires, ou on a besoin que certaines variables spécifiques apparaissent dans des sections spéciales, par exemple pour mapper un matériel particulier. L'attribut section spécifie qu'une variable (ou fonction) existe dans une section particulière. Par exemple, ce petit programme utilise des noms de section :
struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };
char pile[10000] __attribute__ ((section ("PILE"))) = { 0 };
int init_data __attribute__ ((section ("INITDATA"))) = 0;
main()
{
/* Initialiser le pointeur de pile */
init_pile (pile + sizeof (pile));
/* Initialiser les données initialisées */
memcpy (&init_data, &data, &edata - &data);
/* Activer les ports série */
init_duart (&a);
init_duart (&b);
}
Utilisez l'attribut section avec une définition globale initialisée, comme dans l'exemple. GCC émet un avertissement ou ignore l'attribut section dans les déclarations de variables non initialisées. On ne peut utiliser l'attribut section qu'avec une définition globale entièrement initialisée, à cause du fonctionnement de l'éditeur de liens. L'éditeur de liens exige que chaque objet soit défini une seule fois, sauf que les variables non initialisées sont supposées aller dans la section common (ou bss) et peuvent être "définies" plusieurs fois. On peut forcer une variable à être initialisée avec l'option '-fno-common' ou l'attribut nocommon.
Certains formats de fichier ne supportent pas des sections arbitraires, donc l'attribut section n'est pas disponible sur toutes les plateformes. Si vous avez besoin de mapper un module entier dans une section particulière, songez à utiliser les fonctionnalités de l'éditeur de liens à la place.
Attributs de type
Lorsque vous définissez des types structure et union, le mot-clé attribute permet de spécifier des attributs particuliers pour ces types. Ce mot-clé est suivi de spécifications de type entre doubles parenthèses. Quatre attributs sont couramment définis : aligné (aligned), empaqueté (packed), union transparente (transparent-union) et inutilisé (unused). D'autres attributs sont définis pour les fonctions (voir la section 5.26 Déclarer les attributs de fonction) et pour les variables (voir la section 5.33 Spécifier les attributs de variable).
On peut spécifier ces attributs avant ou après le mot-clé. Cela permet de les utiliser dans des fichiers d'en-tête sans déclarer une macro qui aurait le même nom. Par exemple : on peut utiliser _aligned__ au lieu de aligned.
On peut placer la définition ou la déclaration d'énumération, de structure ou de type union entre parenthèses, puis spécifier ses attributs juste après la parenthèse fermante.
On peut également spécifier les attributs sur le tag et le nom d'une énumération, d'une structure ou d'une union, plutôt qu'après la parenthèse fermante.
Voir la section 5.27 Syntaxe des attributs pour une utilisation précise de la syntaxe des attributs.
aligné
Cet attribut spécifie un alignement minimal (en octets) pour le type spécifié. Par exemple, la déclaration suivante :
struct S { short f[3]; } __attribute__ ((aligned (8)));
typedef int int_plus_aligne __attribute__ ((aligned (8)));
Force le compilateur à s'assurer que chaque variable de type S ou chaque entier typedef plus aligné sera alloué et aligné sur au moins 8 octets. Dans les architectures à structure efficace, copier une variable de structure S dans une autre permet au compilateur d'utiliser ldd et std, améliorant ainsi les performances.
Notez que l'alignement de n'importe quel type structure ou union donné est, selon le standard ISO C, au moins un multiple du plus petit commun multiple de tous les alignements des membres de ce type. Cela signifie que l'alignement effectif d'un type structure ou union est le LCM des alignements de tous ses membres. On peut donc efficacement spécifier l'alignement d'un type structure ou union en spécifiant l'attribut aligned sur ses membres. Cependant, l'exemple ci-dessus avec un commentaire explicite est plus clair, plus lisible et plus explicite pour le compilateur pour corriger l'alignement d'un type structure ou union complet.
empaqueté
Cet attribut placé près de la définition d'une énumération, d'une structure ou d'un type union spécifie un alignement minimal requis pour ce type. Spécifier cet attribut pour des structures et des unions est équivalent à spécifier l'attribut packed pour chacun de leurs membres. Spécifier l'option "short-enum" est équivalent à spécifier l'attribut packed sur toutes les définitions d'énumération. On peut également spécifier cet attribut uniquement après les parenthèses, et non dans la déclaration du type, sauf si la déclaration inclut également une définition d'énumération.
union transparente
Cet attribut, basé sur la définition d'une union, indique que certains paramètres de fonction utilisant le type union sont considérés comme une forme spéciale d'appel de fonction. Premièrement, un paramètre compatible avec le type union transparente peut être de n'importe quel type de l'union ; aucune conversion n'est nécessaire. Si l'union contient des types pointeur, un paramètre compatible peut être une constante pointeur nulle ou une expression pointeur nulle. Si l'union contient un pointeur nul, un paramètre compatible peut être une expression pointeur. Si les membres de l'union sont des types entiers, les modificateurs de type sont spécifiés comme pour une conversion de pointeur normale. Deuxièmement, le paramètre est passé à la fonction en utilisant la convention d'appel du premier membre de l'union transparente, pas de l'union elle-même. Tous les membres doivent avoir la même représentation machine ; cela permet au passage de paramètre de se faire correctement.
Les unions transparentes fournissent plusieurs interfaces pour les fonctions de bibliothèque pour gérer les problèmes de compatibilité. Par exemple, supposons que la fonction d'attente doit accepter un entier pour être conforme à Posix, ou qu'une fonction d'attente union doit s'adapter à l'interface 4.1BSD. Le paramètre de la fonction d'attente est de type void, donc la fonction d'attente acceptera divers types de paramètres, mais elle acceptera également d'autres types de pointeur, ce qui réduit l'efficacité de la vérification de type. L'interface définie dans "sys/wait.h" est la suivante :
typedef union {
int *__ip;
union wait *__up;
} statut_attente_ptr_t __attribute ((__transparent_union__));
pid_t attente (statut_attente_ptr_t);
Cette interface permet le passage d'un paramètre entier ou d'un paramètre union attente, avec la convention d'appel d'entier, le programme peut appeler la fonction avec les types de paramètres :
int w1() { int w; return attente(&w); }
int w2() { union wait w; return attente(&w); }
Avec cette interface, l'implémentation de attente serait :
pid_t attente (statut_attente_ptr_t p)
{
return waitpid(-1, p.__ip, 0);
}
Fonctions inline
En déclarant une fonction inline, vous pouvez demander directement à GCC de fusionner le code source de la fonction avec le code de la fonction qui l'appelle. Cela rend l'exécution de la fonction plus rapide en éliminant l'appel de fonction de haut niveau ; en outre, si des arguments sont constants, leurs valeurs connues peuvent permettre des simplifications, de sorte que tout le code de la fonction inline ne soit pas inclus dans le code compilé. L'impact sur la taille du code est à peu près imprévisible ; il peut être plus grand ou plus petit que le code non inline, selon le cas. L'inline est une optimisation et n'est efficace que lors de la compilation avec optimisation. Si vous n'utilisez pas l'option '-O', aucune fonction n'est véritablement inline.
Le standard C99 inclut les fonctions inline, cependant, actuellement, l'implémentation de GCC diffère des exigences du standard C99.
On peut déclarer une fonction inline en utilisant le mot-clé inline dans la déclaration de fonction, comme ceci :
inline int
inc(int *a)
{
(*a)++;
}
(Si vous écrivez un code destiné à être inclus dans un programme standard C dans un fichier d'en-tête, utilisez __inline__ au lieu de inline. Voir la section 5.39 Mots-clés alternatifs.) On peut également rendre toutes les fonctions suffisamment simples inline avec l'option -finline-functions.
Notez que l'utilisation de certaines constructions dans une définition de fonction peut rendre la fonction inappropriée pour l'inline. Ces constructions sont : l'utilisation de fonctions varargs, l'utilisation de alloca, l'utilisation de types de données de taille variable (voir la section 5.14 Tableaux de longueur variable), l'utilisation de goto calculable (voir la section 5.3 Étiquettes assignables), l'utilisation d'instructions goto non locales, et l'utilisation de fonctions imbriquées (voir la section 5.4 Fonctions imbriquées). L'option -Winline peut émettre un avertissement lorsqu'une fonction est marquée inline mais ne peut pas être inline, en expliquant la raison.
Notez que dans C et Objective C, contrairement à C++, le mot-clé inline n'affecte pas la liaison de la fonction.
GCC inline automatiquement les fonctions définies dans un corps de classe en C++ même si elles ne sont pas explicitement déclarées inline. (On peut ignorer cela avec l'option -fno-default-inline ; voir les options contrôlant le dialecte C++.)
Lorsqu'une fonction est à la fois statique et inline, si tous les appels à cette fonction sont fusionnés dans l'appelant et que l'adresse de la fonction n'est jamais utilisée, tout le code assembleur de la fonction n'est pas référencé. Dans ce cas, GCC ne produit effectivement pas de code assembleur pour cette fonction, à moins que vous ne spécifiiez l'option -fkeep-inline-functions. Pour diverses raisons, certains appels ne peuvent pas être fusionnés (en particulier, les appels avant la définition de la fonction et les appels récursifs dans la fonction ne peuvent pas être fusionnés). S'il y a un appel non fusionné, la fonction est compilée comme d'habitude. Si le programme référence son adresse, la fonction doit également être compilée comme d'habitude, car l'adresse ne peut pas être inline.
Lorsqu'une fonction n'est pas statique, le compilateur suppose qu'il peut y avoir des appels dans d'autres fichiers sources ; comme tout symbole global (variable globale) ne peut être défini qu'une fois dans un programme, la fonction ne peut pas être définie dans d'autres fichiers sources, donc les appels là-bas ne peuvent pas être fusionnés. Par conséquent, une fonction non inline est généralement toujours compilée indépendamment.
Si vous spécifiez à la fois inline et extern dans la définition d'une fonction, cette définition n'est utilisée que pour l'inline. Même sans référence explicite à son adresse, la fonction n'est jamais compilée indépendamment. Ces adresses deviennent des références externes, comme si vous aviez simplement déclaré la fonction sans la définir.
Cette combinaison d'inline et d'extern a un effet similaire à une définition de macro. L'utilisation de cette combinaison est de placer la définition de fonction et ces mots-clés dans un fichier d'en-tête, et de placer une autre copie de la définition (sans inline et extern) dans un fichier bibliothèque. La définition dans le fichier d'en-tête rend la plupart des appels à cette fonction inline. S'il y a d'autres fonctions qui en ont besoin, elles consulteront la copie spéciale dans le fichier bibliothèque.
Pour la compatibilité future lorsque GCC implémentera la syntaxe des fonctions inline du standard C99, il est préférable d'utiliser uniquement inline statique. (Lorsque -std=gnu89 est spécifié, la syntaxe actuelle peut être conservée disponible, mais la valeur par défaut finale sera -std=gnu99, et elle implémentera la syntaxe C99, bien qu'elle ne le fasse pas actuellement.)