• Aucun résultat trouvé

Ici un fichier qui accède à globecomme un extern:

//: C03:Global2.cpp {O}

// Accès aux variables globales externes extern int globe;

// (The linker resolves the reference) void func() {

globe = 47;

} ///:~

Le stockage de la variable globeest créé par la définition dans Global.cpp, et le code dans Global2.cppaccède à cette même variable. Comme le code de Global2.cppest compilé séparément du code de Global.cpp, le compilateur doit être informé que la variable existe ailleurs par la déclaration

extern int globe;

A l'exécution du programme, vous verrez que, de fait, l'appel à func( )affecte l'unique instance globale de globe.

Dans Global.cpp, vous pouvez voir la balise de commentaire spéciale (qui est de ma propre conception):

//{L} Global2

Cela dit que pour créer le programme final, le fichier objet Global2doit être lié (il n'y a pas d'extension parce que l'extension des fichiers objets diffère d'un système à l'autre). Dans Global2.cpp, la première ligne contient aussi une autre balise de commentaire spéciale {O},qui dit "n'essayez pas de créer un exécutable à partir de ce fichier, il est en train d'être compilé afin de pouvoir être lié dans un autre exécutable." Le programme ExtractCode.cppdans le deuxième volume de ce livre (téléchargeable à www.BruceEckel.com) lit ces balises et crée le makefileapproprié afin que tout se compile proprement (vous étudierez les makefiles à la fin de ce chapitre).

3.6.2 - Variables locales

Les variables locales existent dans un champ limité ; elles sont "locales" à une fonction. Elle sont souvent appelées variables automatiquesparce qu'elles sont créés automatiquement quand on entre dans le champ et disparaissent automatiquement quand le champ est fermé. Le mot clef autorend la chose explicite, mais les variables locales

sont par défaut autoafin qu'il ne soit jamais nécessaire de déclarer quelque chose auto.

Variables de registre

Une variable de registre est un type de variable locale. Le mot clef registerdit au compilateur "rend l'accès à cette donnée aussi rapide que possible". L'accroissement de la vitesse d'accès aux données dépend de l'implémentation, mais, comme le suggère le nom, c'est souvent fait en plaçant la variable dans un registre. Il n'y a aucune garantie que la variable sera placée dans un registre ou même que la vitesse d'accès sera augmentée.

C'est une suggestion au compilateur.

Il y a des restrictions à l'usage des variables de registre. Vous ne pouvez pas prendre ou calculer leur adresse.

Elles ne peuvent être déclarées que dans un bloc (vous ne pouvez pas avoir de variables de registreglobales ou static). Toutefois, vous pouvez utiliser une variable de registrecomme un argument formel dans une fonction (i.e., dans la liste des arguments).

En général, vous ne devriez pas essayer de contrôler l'optimiseur du compilateur, étant donné qu'il fera probablement un meilleur travail que vous. Ainsi, il vaut mieux éviter le mot-clef register.

3.6.3 - static

Le mot-clef statica différentes significations. Normalement, les variables définies dans une fonction disparaissent à la fin de la fonction. Quand vous appelez une fonction à nouveau, l'espace de stockage pour la variable est recréé et les valeurs ré-initialisées. Si vous voulez qu'une valeur soit étendue à toute la durée de vie d'un programme, vous pouvez définir la variable locale d'une fonction staticet lui donner une valeur initiale. L'initialisation est effectuée uniquement la première fois que la fonction est appelée, et la donnée conserve sa valeur entre les appels à la fonction. Ainsi, une fonction peut "se souvenir" de morceaux d'information entre les appels.

Vous pouvez vous demander pourquoi une variable globale n'est pas utilisée à la place ? La beauté d'une variable staticest qu'elle est indisponible en dehors du champ de la fonction et ne peut donc être modifiée par inadvertance. Ceci localise les erreurs.

Voici un exemple d'utilisation des variables static:

//: C03:Static.cpp // Utiliser une variable static dans une fonction

#include <iostream>

using namespace std;

void func() { static int i = 0;

cout << "i = " << ++i << endl;

}

int main() {

for(int x = 0; x < 10; x++) func();

} ///:~

A chaque fois que func( ) est appelée dans la boucle for, elle imprime une valeur différente. Si le mot-clef staticn'est pas utilisé, la valeur utilisée sera toujours ‘1’.

Le deuxième sens de staticest relié au premier dans le sens “indisponible en dehors d'un certain champ”. Quand staticest appliqué au nom d'une fonction ou à une variable en dehors de toute fonction, cela signifie “Ce nom est indisponible en dehors de ce fichier.” Le nom de la focntion ou de la variable est local au fichier ; nous disons qu'il a la portée d'un fichier. Par exemple, compiler et lier les deux fichiers suivants causera une erreur d'édition de liens

:

//: C03:FileStatic.cpp

// Démonstration de la portée à un fichier. Compiler et // lier ce fichier avec FileStatic2.cpp

// causera une erreur d'éditeur de liens

// Portée d'un fichier signifie disponible seulement dans ce fichier : static int fs;

int main() { fs = 1;

} ///:~

Même si la variable fsest déclaré exister comme une externdan le fichier suivant, l'éditeur de liens ne la trouvera pas parce qu'elle a été déclarée staticdans FileStatic.cpp.

//: C03:FileStatic2.cpp {O}

// Tentative de référencer fs extern int fs;

void func() { fs = 100;

} ///:~

Le mot-clef staticpeut aussi être utilisé dans une classe. Ceci sera expliqué plus loin, quand vous aurez appris à créer des classes.

3.6.4 - extern

Le mot-clef externa déjà été brièvement décrit et illustré. Il dit au compilateur qu'une variable ou une fonction existe, même si le compilateur ne l'a pas encore vu dans le fichier en train d'être compilé. Cette variable ou cette fonction peut être définie dans un autre fichier ou plus loin dans le même fichier. Comme exemple du dernier cas :

//: C03:Forward.cpp // Fonction forward & déclaration de données

#include <iostream>

using namespace std;

// Ce n'est pas vraiment le cas externe, mais il

// faut dire au compilateur qu'elle existe quelque part : extern int i;

extern void func();

int main() { i = 0;

func();

}

int i; // Création de la donnée void func() {

i++;

cout << i;

} ///:~

Quand le compilateur rencontre la déclaration ‘ extern int i’, il sait que la définition de idoit exister quelque part comme variable globale. Quand le compilateur atteint la définition de i, il n'y a pas d'autre déclaration visible, alors il sait qu'il a trouvé le même idéclaré plus tôt dans le fichier. Si vous définissiez i static, vous diriez au compilateur que iest défini globalement (via extern), mais qu'il a aussi une portée de fichier (via static), et le compilateur génèrera une erreur.

Edition de lien

Pour comprendre le comportement des programmes en C et C++, vous devez connaître l'édition de liens ( linkage).

Dans un programme exécutable un identifiant est représenté par un espace mémoire qui contient une variable ou le corps d'une fonction compilée. L'édition de liens décrit cet espace comme il est vu par l'éditeur de liens ( linker).

Il y a deux types d'édition de liens : l'édition de liens interneet externe.

L'édition de liens interne signifie que l'espace mémoire est créé pour représenter l'identifiant seulement pour le fichier en cours de compilation. D'autres fichiers peuvent utiliser le même nom d'identifiant avec l'édition interne de liens, ou pour une variable globale, et aucun conflit ne sera détecté par l'éditeur de liens – un espace différent est créé pour chaque identifiant. L'édition de liens interne est spécifiée par le mot-clef staticen C et C++.

L'édition de liens externe signifie qu'un seul espace de stockage est créé pour représenter l'identifiant pour tous les fichiers compilés. L'espace est créé une fois, et l'éditeur de liens doit assigner toutes les autres références à cet espace. Les variables globales et les noms de fonctions ont une édition de liens externe. Ceux-ci sont atteints à partir des autres fichiers en les déclarant avec le mot-clef extern. Les variables définies en dehors de toute fonction (à l'exception de conten C++) et les définitions de fonctions relèvent par défaut de l'édition de liens externe. Vous pouvez les forcer spécifiquement à avoir une édition interne de liens en utilisant le mot-clef static.

Vous pouvez déclarer explicitement qu'un identifiant a une édition de liens externe en le définissant avec le mot-clef extern. Définir une variable ou une fonction avec externn'est pas nécessaire en C, mais c'est parfois nécessaire pour consten C++.

Les variables (locales) automatiques existent seulement temporairement, sur la pile, quand une fonction est appelée. L'éditeur de liens ne connaît pas les variables automatiques, et celles-ci n'ont donc pas d'édition de liens.

3.6.5 - Constantes

Dans l'ancien C (pré-standard), si vous vouliez créer une constante, vous deviez utiliser le préprocesseur :

#define PI 3.14159

Partout où vous utilisiez PI, la valeur 3.14159 était substitué par le préprocesseur (vous pouvez toujours utiliser cette méthode en C et C++).

Quand vous utilisez le préprocesseur pour créer des constantes, vous placez le contrôle de ces constantes hors de la portée du compilateur. Aucune vérification de type n'est effectuée sur le nom PIet vous ne pouvez prendre l'adresse de PI(donc vous ne pouvez pas passer un pointeur ou une référence à PI). PIne peut pas être une variable d'un type défini par l'utlisateur. Le sens de PIdure depuis son point de définition jusqu'à la fin du fichier ; le préprocesseur ne sait pas gérer la portée.

C++ introduit le concept de constante nommée comme une variable, sauf que sa valeur ne peut pas être changée.

Le modificateur constdit au compilateur qu'un nom représente une constante. N'importe quel type de données, prédéfini ou défini par l'utilisateur, peut être défini const. Si vous définissez quelque chose constet essayez ensuite de le modifier, le compilateur génère une erreur.

Vous devez définir le type de const, ainsi :

const int x = 10;

En C et C++ standard, vous pouvez utiliser une constante nommée dans une liste d'arguments, même si l'argument auquel il correspond est un pointeur ou un référence (i.e., vous pouvez prendre l'adresse d'une const).

Une consta une portée, exactement comme une variable normale, vous pouvez donc "cacher" une constdans une

fonction et être sûr que le nom n'affectera pas le reste du programme.

consta été emprunté au C++ et incorporé en C standard, mais de façon relativement différente. En C, le compilateur traite une constcomme une variable qui a une étiquette attachée disant "Ne me changez pas". Quand vous définissez une consten C, le compilateur crée un espace pour celle-ci, donc si vous définissez plus d'une constavec le même nom dans deux fichiers différents (ou mettez la définition dans un fichier d'en-tête ( header)), l'éditeur de liens générera des messages d'erreur de conflits. L'usage voulu de consten C est assez différent de celui voulu en C++ (pour faire court, c'est plus agréable en C++).

Valeurs constantes

En C++, une constdoit toujours avoir une valeur d'initialisation (ce n'est pas vrai en C). Les valeurs constantes pour les types prédéfinis sont les types décimal, octal, hexadécimal, nombres à virgule flottante ( floating-point numbers) (malheureusement, les nombres binaires n'ont pas été considérés importants), ou caractère.

En l'absence d'autre indication, le compilateur suppose qu'une valeur constante est un nombre décimal. Les nombres 47, 0 et 1101 sont tous traités comme des nombres décimaux.

Une valeur constante avec 0 comme premier chiffre est traitée comme un nombre octal (base 8). Les nombres en base 8 peuvent contenir uniquement les chiffres 0-7 ; le compilateur signale les autres chiffres comme des erreurs.

Un nombre octal valide est 017 (15 en base 10).

Une valeur constante commençant par 0x est traitée comme un nombre hexadécimal (base 16). Les nombres en base 16 contiennent les chiffres 0 à 9 et les lettres A à F. Un nombre hexadécimal valide peut être 0x1fe (510 en base 10).

Les nombres à virgule flottante peuvent contenir un point décimal et une puissance exponentielle (représentée par e, ce qui veut dire "10 à la puissance"). Le point décimal et le esont tous deux optionnels. Si vous assignez une constante à une variable en virgule flottante, le compilateur prendra la valeur constante et la convertira en un nombre à virgule flottante (ce procédé est une forme de ce que l'on appelle la conversion de type implicite).

Toutefois, c'est une bonne idée d'utiliser soit un point décimal ou un epour rappeler au lecteur que vous utilisez un nombre à virgule flottante ; des compilateurs plus anciens ont également besoin de cette indication.

Les valeurs constantes à virgule flottante valides sont : 1e4, 1.0001, 47.0, 0.0, et -1.159e-77. Vous pouvez ajouter des suffixes pour forcer le type de nombre à virgule flottante : fou Fforce le type float, Lou lforce le type long double; autrement le nombre sera un double.

Les caractères constants sont des caractères entourés par des apostrophes, comme : ‘ A’, ‘ 0’, ‘ ‘. Remarquer qu'il y a une grande différence entre le caractère ‘ 0’ (ASCII 96) et la valeur 0. Des caractères spéciaux sont représentés avec un échappement avec “backslash”: ‘ \n’ (nouvelle ligne), ‘ \t’ (tabulation), ‘ \\’ (backslash), ‘ \r’

(retour chariot), ‘ "’ (guillemets), ‘ '’ (apostrophe), etc. Vous pouvez aussi exprimer les caractères constants en octal : ‘ \17’ ou hexadecimal : ‘ \xff’.

3.6.6 - volatile

Alors que la déclaration constdit au compilateur “Cela ne change jamais” (ce qui permet au compilateur d'effectuer des optimisations supplémentaires), la déclaration volatiledit au compilateur “On ne peut pas savoir quand cela va changer” et empêche le compilateur d'effectuer des optimisations basée sur la stabilité de cette variable. Utilisez ce mot-clef quand vous lisez une valeur en dehors du contrôle de votre code, comme un registre dans une partie de communication avec le hardware. Une variable volatileest toujours lue quand sa valeur est requise, même si elle a été lue à la ligne précédente.

Un cas spécial d'espace mémoire étant “en dehors du contrôle de votre code” est dans un programme multithreadé. Si vous utilisez un flag modifié par une autre thread ou process, ce flag devrait être volatileafin que le compilateur ne suppose pas qu'il peut optimiser en négligeant plusieurs lectures des flags.

Notez que volatilepeut ne pas avoir d'effet quand un compilateur n'optimise pas, mais peut éviter des bugs critiques quand vous commencez à optimiser le code (c'est alors que le compilateur commencera à chercher des lectures redondantes).

Les mots-clefs constet volatileseront examinés davantage dans un prochain chapitre.