[C++] instance globale dans une lib statique

Bonjour à tous, je viens de tomber sur un truc vraiment étrange. Ca tient en trois fichiers C++ et en deux projets.

Le premier est un programme en ligne de commande, un seul fichier, main.cpp :

[code]#include <stdio.h>
#include « …/lib/lib.h »

int main() {

//CTest2::hello();
getchar();
return 0;

}[/code]

J’explique après l’utilité de la ligne en commentaire. L’autre projet est une librairie statique, lib.h :

class CTest2 { public : static void hello(); };

et lib.cpp :

[code]#include <stdio.h>
#include « lib.h »

class CTest1 {
public : CTest1() {
 printf(« test1\n »);
}
} test1;

void CTest2::hello() {
printf(« hello\n »);
}[/code]

J’ai donc une instance globale de la class CTest1 : test1. Je m’attends donc à ce que le constructeur qui affiche « test1\n » soit appelé systématiquement.

Une précision : J’utilise MSVC et j’ai testé avec le 6 et .NET ça fait la même chose.

Mon programme en ligne de commande a une dependency vers la lib. Si je compile avec la ligne commentée dans main.cpp, le programme n’affiche rien du tout. Si je réactive la ligne, le programme affiche « test1 » suivi de « hello ». Si je re-commente la ligne, le programme n’affiche plus que « test1 ». Si je fais un clean build, le programme n’affiche plus rien. :stuck_out_tongue:

Alors ma question est : quel est le comportement correct que je devrais attendre d’un tel code ? Mon opinion est que à partir du moment que je link avec la lib, mon instance globale test1 devrait être créée. Mais qu’en est-il vraiment ?

En commentant la ligne CTest2::hello(); tu n’as plus aucune référence (dans ton fichier main) vers la lib, donc l’éditeur des liens ne lie pas ton appli avec la bibliothèque, donc pas de création d’objet (donc pas de sortie sur stdout) :P.

EDIT : pas sûr d’être clair mais je pourrais préciser.

[quote name=‹ Moktar › date=’ 16 Dec 2004, 16:40’]En commentant la ligne CTest2::hello(); tu n’as plus aucune référence (dans ton fichier main) vers la lib, donc l’éditeur des liens ne lie pas ton appli avec la bibliothèque, donc pas de création d’objet (donc pas de sortie sur stdout) :P.

EDIT : pas sûr d’être clair mais je pourrais préciser.
[right][post=« 313650 »]<{POST_SNAPBACK}>[/post][/right][/quote]

Y’a pas de souci c’est tout à fait clair :stuck_out_tongue: Mais le truc c’est que ça je sais déjà… Ce que je me demande c’est si c’est le comportement qu’on est en droit d’attendre d’un compilateur C++ ? En plus de ça, tous mes settings sont en « debug » : aucune optimisation. Donc il ne devrait théoriquement pas jeter les bouts de code qu’il pense inutilisés pour optimiser l’executable.

[quote name=‹ Drealmer › date=’ 16 Dec 2004, 17:11’]Y’a pas de souci c’est tout à fait clair :stuck_out_tongue: Mais le truc c’est que ça je sais déjà… Ce que je me demande c’est si c’est le comportement qu’on est en droit d’attendre d’un compilateur C++ ? En plus de ça, tous mes settings sont en « debug » : aucune optimisation. Donc il ne devrait théoriquement pas jeter les bouts de code qu’il pense inutilisés pour optimiser l’executable.
[right][post=« 313660 »]<{POST_SNAPBACK}>[/post][/right][/quote]

Ce n’est pas une optimisation. C’est le principe même d’une bibliothèque. De plus, la granularité n’est pas le bout de code mais le fichier objet.

[quote name=‘Moktar’ date=’ 16 Dec 2004, 17:17’]Ce n’est pas une optimisation. C’est le principe même d’une bibliothèque. De plus, la granularité n’est pas le bout de code mais le fichier objet.
[right][post=“313663”]<{POST_SNAPBACK}>[/post][/right][/quote]

Donc en gros l’idée c’est que c’est normal ? Ca me semble tout de même bizarre… Ce comportement est défini par la norme C++ ou bien c’est laissé à la discretion du compilateur ?

Oui et aussi, dans ce cas-là y’a-t-il une option pour forcer MSVC à tout linker ?

Et puis sinon, plus j’y penses plus je trouves que tu as raison : une lib ce n’est jamais qu’une archive contenant des objets. Le linker ne link donc pas avec toute la lib d’un coup, mais avec les éléments qu’elle contient… C’est pas intuitif ce truc.

Précision : les dépendances sous MSVC semblent mal gérées cf . « Si je re-commente la ligne, le programme n’affiche plus que « test1 »
:P »

Ce n’est pas spécifique à C++ ce comportement. C’est la définition d’une lib en C/C++ (et non pas laissé à la discrétion du linker).

Forcer le link ? mmffff… à ma connaissance non (dans une DLL oui, bien sur mais ce n’est pas ton cas ici).

Forcer le link, c’est en fait ne pas passer par une lib, c.a.d. linker tous les .o avec l’appli :stuck_out_tongue:

C’est bel et bien le comportement normal.

En fait, pour être précis, le comportement est le suivant : si une classe est définie en globale dans un fichier C ou CPP, et qu’aucune référence à l’un des éléments définis dans ce fichier n’est présent dans l’application, alors l’objet global ne sera pas présent lui non plus au link. Au contraire, si une référence vers n’importe quel élément est présent dans l’application, alors l’objet global sera présent lui aussi. A priori, il n’y a pas forcément besoin que la référence soit en rapport direct avec l’objet.

C’est comme ça que fonctionne le linker en C/C++, c’est dans la norme (le problème est identique sur tous les compilos que j’ai testé en tout cas, je suppose donc que c’est dans la norme, sinon aucun compilo n’est standard :P)

Ca s’appels le Dead Code Stripping, et il me semble qu’on peut le disabler dans VC6,7 et 7.1, mais je ne sais plus comment…
Il me semble aussi que pour les object statics comme ce que tu decris : la plus part des compilo ont un pragma pour dire : « dans la vie tu fais ce que tu veux, mais celui la, tu le laisse la ou il est », mais je ne me rappels plus ou c’est non plus.

De toute facons : un object static : c’estpastop, alors un object static dans une lib… yuuuurrrrk ! :stuck_out_tongue:

[quote name=‹ c0unt0 › date=’ 16 Dec 2004, 20:46’]Ca s’appels le Dead Code Stripping, et il me semble qu’on peut le disabler dans VC6,7 et 7.1, mais je ne sais plus comment…
Il me semble aussi que pour les object statics comme ce que tu decris : la plus part des compilo ont un pragma pour dire : « dans la vie tu fais ce que tu veux, mais celui la, tu le laisse la ou il est », mais je ne me rappels plus ou c’est non plus.[/quote]

Ouep ça serait bien pratique à savoir ça… Mais bon dans l’immédiat, j’ai directement mis un lien vers le code de la lib dans le prog et ça tourne comme un charme.

Oui c’est certain, j’approuve totalement. Le problème c’est que comme partout, y’a des morceaux de code qui voyagent de projet en projet et sur lequel tout monde est d’accord pour dire que si c’était à refaire on le referait pas comme ça… mais dans l’immédiat faut faire avec parce que ça fonctionne, et que c’est tout ce dont on a besoin :stuck_out_tongue: et effectivement le problème en question se trouve dans l’un de ces « bouts » de code. (bien couillu le bout de code quand même)

Merci pour toutes vos réponses !

Je ne crois pas que ça ait a voir avec le dead code stripping en fait. Il me semble que c’est le comportement dans tous les cas du linker… Je n’ai pas essayé toutes les options, mais j’en avais essayé pas mal quand même, sans réussir à lui faire linker ce cas particulier…

De toute façon, les globales, c’est mal :stuck_out_tongue: Mieux vaut un singleton que tu initialises au premier appel à une méthode statique Get, au moins tu maitrises un peu mieux son initialisation et surtout l’ordre des initialisations quand tu en as plusieurs :stuck_out_tongue: Mais ça reste de toute façon pas tip top.

Bon, a l’arrache est bourré :
Je crois que le compilo a une option qui (activée par défaut) vire les libs innutilisées (et qui doit aussi virer les bouts de libs innutilisées).

Le comportement différent entre le build et le rebuild viens juste du fait que le link est incrémental.

[quote name=‹ Tzim › date=’ 17 Dec 2004, 00:18’]Bon, a l’arrache est bourré :
[right][post=« 313846 »]<{POST_SNAPBACK}>[/post][/right][/quote]

« Quoi !? Tu bois durant la semaine !? » :stuck_out_tongue:

Héhé, c’est ce que m’a dit un collègue hier matin quand je lui ai demandé de l’aspirine B) (pourtant je le sais qu’il faut pas mélanger trappiste et vin blanc)

[quote name=‹ tuo › date=’ 16 Dec 2004, 23:29’]De toute façon, les globales, c’est mal :stuck_out_tongue: Mieux vaut un singleton que tu initialises au premier appel à une méthode statique Get, au moins tu maitrises un peu mieux son initialisation et surtout l’ordre des initialisations quand tu en as plusieurs :stuck_out_tongue: Mais ça reste de toute façon pas tip top.
[right][post=« 313824 »]<{POST_SNAPBACK}>[/post][/right][/quote]

Ouep, singletons partout dans le code récent, les globales c’est mal on est tous d’accord :stuck_out_tongue: Mais comme j’ai dit, j’ai pas des masses de choix, la lib en question est un vieux morceau de code très générique qu’on utilise pour des tonnes de trucs. Alors c’est bourré de défauts, on réécrirait sûrement tout assez différemment maintenant, mais vu que ça fonctionne et qu’on n’a pas une folle envie de passer deux mois à la refaire…

[quote name=‹ Tzim › date=’ 17 Dec 2004, 00:18’]Bon, a l’arrache est bourré :
Ca s’est vu  :stuck_out_tongue:

Je crois que le compilo a une option qui (activée par défaut) vire les libs innutilisées (et qui doit aussi virer les bouts de libs innutilisées).
Mais non, d’une c’est le boulot du linker, deux, c’est le comportement normal d’une lib. C’est étudié pour et c’est la raison d’être d’une lib.

Le comportement différent entre le build et le rebuild viens juste du fait que le link est incrémental.
Mffff. Au début j’avais dit non, mais en réfléchissant j’ai un doute (je ne suis pas bourré ça aide) :P. C’est possible mais en même temps j’ai du mal à concevoir que l’incrémental puisse induire une erreur de ce type mais je me trompe peut-être. D’autres avis ?[/quote]

[quote name=‘Moktar’ date=’ 17 Dec 2004, 09:30’]Le comportement différent entre le build et le rebuild viens juste du fait que le link est incrémental.
Mffff. Au début j’avais dit non, mais en réfléchissant j’ai un doute (je ne suis pas bourré ça aide) biggrin.gif. C’est possible mais en même temps j’ai du mal à concevoir que l’incrémental puisse induire une erreur de ce type mais je me trompe peut-être. D’autres avis ?[/quote]

Ah non là je suis d’accord, le link incrémental ça fait parfois des choses très bizarres. Tout comme la recompilation partielle. Cette dernière se fait avoir assez facilement quand on a plein de fonctions virtuelles… Un bon rebuild forcé résoud bien des problèmes.

[quote name=‹ Drealmer › date=’ 17 Dec 2004, 09:37’]Ah non là je suis d’accord, le link incrémental ça fait parfois des choses très bizarres. Tout comme la recompilation partielle. Cette dernière se fait avoir assez facilement quand on a plein de fonctions virtuelles… Un bon rebuild forcé résoud bien des problèmes.
[right][post=« 313901 »]<{POST_SNAPBACK}>[/post][/right][/quote]

En effet, le link & build incremental ne sont pas forcément safe dans tous les cas, mais c’est quand même bien pratique :stuck_out_tongue:

Concernant le problème initial, il me semble que le fait que le global soit viré vient justement du fait qu’il est viré AVANT la phase de dead code remove.

Le dead code removing se fait en fait a partir de l’application finale linkée. Le programme regarde les bouts de code qui ne sont référencés par personne, et les supprime de l’executable final, réduisant la taille. Or, normalement la variable globale doit être référencée, par son constructeur en fait, dans la pre-initialisation de l’application (la fameuse CRT). Donc théoriquement le dead code remover ne devrait pas y toucher. D’ailleurs il suffit d’utiliser UN element présent dans le .obj où la classe globale est définie pour qu’elle soit linkée… Parce que la granularité de la sélection se fait à l’OBJ et pas à la classe ou à la fonction.

[quote name=‹ tuo › date=’ 17 Dec 2004, 17:24’]…/…
D’ailleurs il suffit d’utiliser UN element présent dans le .obj où la classe globale est définie pour qu’elle soit linkée… Parce que la granularité de la sélection se fait à l’OBJ et pas à la classe ou à la fonction.
[right][post=« 314139 »]<{POST_SNAPBACK}>[/post][/right][/quote]

Absolument.

J’en profite pour recommander de faire des *.o tout petit, genre une fonction (en C) par fichier source (et donc fichier objet) de façon à rassembler tous ces petits *.o dans une bibliothèque, ce qui permettera au linker de faire l’édition des liens avec le strict minimum de *.o.
Pas sur d’être clair :confused:

La runtime C est construite comme ça. Par exemple, printf (que tout le monde connait) est UN seul fichier source et donc un seul fichier OBJET. Il en va de même pour la majorité (la totalité ?) des fonctions de la runtime.

Ma contribution.