Articles tagués “statique

C vs C++ : initialisation des variables statiques

On lit souvent C/C++. Hé hé… S’il est indéniable que le ++ de C++ n’est pas là pour rien, il faut aussi savoir qu’il y a des différences en forme de pièges dans la partie « commune ». Ces différences sont particulièrement trompeuses quand on écrit du code C++ qu’on souhaite être compatible C. On va s’intéresser aujourd’hui à l’initialisation des variables statiques.

Voici un code C++ a priori anodin :

#include <stdlib.h>

int getValue()
{
    return rand();
}

int f(int c)
{
    static const int initial = getValue();

    if (initial != c)
        return c;
    else
        return initial;
}

int main()
{
    return f(42);
}

La ligne intéressante est la ligne 10 : on initialise une variable statique avec la valeur de retour d’une fonction.

Plaçons ce code dans un fichier main.cpp, plaçons ce fichier dans un projet Eclipse, et laçons un build :

[pre]16:52:21 **** Incremental Build of configuration Debug for project Experiences ****
Info: Internal Builder is used for build
g++ -std=c++0x -O0 -g3 -pedantic -Wall -Wextra
-c -o « src\\main.o » « ..\\src\\main.cpp »
g++ -o Experiences.exe « src\\main.o »

16:52:21 Build Finished (took 498ms)[/pre]

Renommons simplement le fichier pour forcer sa compilation en C. En effet, GCC se base sur l’extension du fichier pour savoir comment le compiler. Voici le résultat du build dans ce cas :

[pre]16:53:12 **** Incremental Build of configuration Debug for project Experiences ****
Info: Internal Builder is used for build
gcc -std=c99 -O0 -g3 -pedantic -Wall -Wextra
-c -o « src\\main.o » « ..\\src\\main.c »
..\src\main.c: In function ‘f’:
..\src\main.c:10:32: error: initializer element is not constant
static const int initial = getValue();
^

16:53:12 Build Finished (took 139ms)
[/pre]

L’erreur est claire. On pourrait soupçonner le qualificatif const mais cela ne change rien si on l’enlève. Le résultat est le même si la variable est statique au fichier au lieu d’être statique à la fonction.

Sans aller creuser dans les spécifications des langages, on se doute que C résoud la valeur d’une variable statique à la compilation alors que C++ autorise qu’elle soit résolue à l’exécution. Pour cela, le compilateur va sans doute générer un peu de code pour savoir si la variable a déjà été initialisée et l’initialiser le cas échéant (inutile de préciser que cela a un coût). On peut le vérifier en regardant le code assembleur de la fonction f() :

[pre] f(int):
0040144d: push %ebp
0040144e: mov %esp,%ebp
00401450: sub $0x18,%esp
10 static const int initial = getValue();
00401453: mov $0x40d020,%eax
00401458: movzbl (%eax),%eax
0040145b: test %al,%al
0040145d: jne 0x40148a
0040145f: movl $0x40d020,(%esp)
00401466: call 0x4014c4
0040146b: test %eax,%eax
0040146d: setne %al
00401470: test %al,%al
00401472: je 0x40148a
00401474: call 0x401440
00401479: mov %eax,0x40d028
0040147e: movl $0x40d020,(%esp)
00401485: call 0x4014bc
12 if (initial != c)
0040148a: mov 0x40d028,%eax
0040148f: cmp 0x8(%ebp),%eax
00401492: je 0x401499
13 return c;
00401494: mov 0x8(%ebp),%eax
00401497: jmp 0x40149e
15 return initial;
00401499: mov 0x40d028,%eax
16 }
[/pre]

Changeons l’initialisation de notre variable par quelque chose de constant, par exemple :

static const int initial = 666;

Voici le nouveau code assembleur, beaucoup plus simple, de la même fonction f() :
[pre] f(int):
0040144d: push %ebp
0040144e: mov %esp,%ebp
12 if (initial != c)
00401450: cmpl $0x29a,0x8(%ebp)
00401457: je 0x40145e
13 return c;
00401459: mov 0x8(%ebp),%eax
0040145c: jmp 0x401463
15 return initial;
0040145e: mov $0x29a,%eax
16 }
[/pre]

Pour avoir un code similaire en C, il faudrait implémenter une protection similaire, comme ceci :

#include <stdbool.h>
int f(int c)
{
    static int initial;
    static bool guard = false;

    if (guard == false)
    {
        guard = true;
        initial = rand();
    }

    if (initial != c)
        return c;
    else
        return initial;
}

On voit quelques inconvénients : perte de lisibilité, perte du qualificatif const, aucune garantie de thread-safety. A noter qu’en C++, le caractère thread-safe d’un tel code semble dépendre de la version du langage, comme débattu dans cette discussion stackoverflow « Is local static variable initialization thread-safe in C++11?« .

C’est tout pour aujourd’hui, vous êtes prévenus 🙂

Édition du 05/04/2017 : il s’avère en fait que la nécessité d’un initializer constant en C ne se limite pas aux variables déclarés avec le mot-clé static. Cette contrainte s’applique à toutes les variables ayant une static storage duration, comme expliqué dans cette discussion stackoverflow « Error “initializer element is not constant” when trying to initialize variable with const« . Ainsi, cette contrainte s’applique aussi aux variables globales.


Créer et utiliser une bibliothèque statique en C

Il est tout simple de créer une bibliothèque statique en C pour ensuite s’en servir dans un autre programme. Dans la suite, j’expliquerai comment faire en ligne de commande puis avec Code::Blocks. Je reprend largement les explications données en anglais ici pour la création.

En ligne de commande

Commençons par faire tout ceci en ligne de commande, dans notre terminal. J’utilise dans la suite gcc sous Ubuntu. Je commence par créer le fichier source util.c de la bibliothèque:

#include <stdio.h>

void UTIL_says_hello(void)
{
	puts("UTIL says hello !");
}

Je crée aussi le fichier d’en-tête util.h qui me permettra de faire connaitre à un autre programme la fonction proposée par ma bibliothèque.

void UTIL_says_hello(void);

Je compile mon fichier avec l’option -c pour simplement générer un fichier objet et non un exécutable :

gcc -Wall -c util.c

J’utilise ensuite ar pour générer l’archive (voir ici pour les options) :

ar crs libutil.a util.o

Je me retrouve avec mon splendide fichier .a accompagné de son fichier .h. On peut utiliser readelf -sW dessus pour voir les symboles qu’il contient, on voit qu’il y a notre fonction et puts. Je vous laisse faire la manipulation si vous voulez voir le résultat.

Vient le moment d’utiliser cette bibliothèque. Je crée pour cela un second fichier source où j’utilise la fonction de ma bibliothèque :

#include "util.h"

int main(void)
{
	UTIL_says_hello();
	return 0;
}

Je génère mon exécutable avec gcc :

gcc -Wall main.c -L. -lutil

L’option -L. permet d’ajouter le dossier courant (le dossier .) à la liste des dossiers où le linker ira chercher les bibliothèques puisque c’est là que se trouve ma bibliothèque. L’option -lutil ajoute la bibliothèque util (on remarquera que le lib est sous-entendu) à la liste des bibliothèques à trouver et à utiliser. L’option -Wall… Vous prenez mon pied aux fesses et vous vous dirigez vers Google si elle vous est inconnue. J’obtiens un fichier que je peux exécuter :

$ ./a.out 
UTIL says hello !

Avec Code::Blocks

Si vous utilisez CodeBlocks, il est encore plus simple de créer une bibliothèque statique. Il suffit de faire File / New / Project… et de choisir Static Library. Vous écrivez vos fichiers sources et vous cliquez sur Build. On peut voir dans la console qu’il fait la même chose que ce qu’on a fait précédemment avec gcc dans le terminal. Voici ce que ça donne sous Windows 7 avec MinGW :

------------- Build: Debug in util (compiler: GNU GCC Compiler)---------------

mingw32-gcc.exe  -Wall -g   
-c D:\Users\pgradot\Documents\util\main.c -o out\Debug\main.o
ar.exe -r -s libutil.a out\Debug\main.o 
ar.exe: creating libutil.a
Output size is 2.31 KB
Process terminated with status 0 (0 minutes, 0 seconds)
0 errors, 0 warnings (0 minutes, 0 seconds)

Il ne reste plus qu’à écrire le fichier d’en-tête.

Si vous souhaitez utiliser une bibliothèque dans Code::Blocks, il faut aller dans les options du projet dans le menu Project / Build options…, sélectionner la cible qui va bien et de modifier les options du linker. Pour une bibliothèque, je conseille de sélectionner la configuration parente pour que toutes les configurations connaissent la bibliothèque. Dans la capture d’écran suivante, vous pouvez voir que je sélectionne bien util qui est la configuration parente. Les configurations debug et release héritent des propriétés de leur parente :
add_static_lib_codeblocks
Il y a deux solutions. La première est renseigner les options en mode texte dans le champ de texte à droite, comme dans le terminal. La seconde est de cliquer sur le bouton Add dans la zone de gauche, ce qui ouvre une fenêtre d’exploration pour aller chercher le fichier souhaité.