OVH Cloud OVH Cloud

symbole non référencé dans une librairie statique

15 réponses
Avatar
Snoopy
Hello,

Il m'arrive de d=E9finir des objets que je me contente de stoquer dans une =
cha=EEne de pointeurs. Ces objets sont des membres statiques de classe et s=
'auto-inscrivent dans la cha=EEne =E0 leur cr=E9ation. Mon probl=E8me est l=
e suivant : Les objets n'etant jamais r=E9f=E9ranc=E9s directement, mon com=
pilo (gcc 3.3.5) se permet de les supprimer !

d=E9tails :
---------
J'utilise un 'pseudo RTTI' pour construire des objets =E0 partir d'un fichi=
er de config. (et donc =E0 partir de cha=EEne de char). Tous le code ci-des=
sous est dans une librairie statique (sinon, ca fonctionne normalement)

Mes objets ressemblent =E0 ca :
-----------------------------
class obj : public objBase {
private:
static objFactory _factory;
};


L'usine d'objets ressemble =E0 =E7a :
---------------------------------
typedef objBase (*objCtor)();

class objFactory {
public:
objFactory(string name, objCtor ctor) :=20
_name(name),
_ctor(ctor),=20
_next(_first) {
_first =3D this;
}
=20
string _name;
objCtor _ctor;
objFactory* _next;
static objFactory* _first;
};

Impl=E9mentation de l'objet :
---------------------------
objBase* ctorForObj() {
return new obj;
}
objFactory obj::_factory("Obj", (objCtor) ctorForObj);
=20

Fonction de construction via RTTI :
-----------------------------------
objBase* create(string name) {
objFactory* factory =3D objFactory::_first;
while (factory) {
if (factory->_name =3D=3D name)=20
return factory->_ctor();=20
factory =3D factory->_next;
}
return NULL;
}


Conclusion :
-----------
Je me retrouve avec un objet non r=E9f=E9renc=E9 =E0 l'ext=E9rieur de la li=
b (obj) qui contient un objet statique encore moin r=E9f=E9renc=E9 (obj::fa=
ctory). =E0 l'=E9ddition des liens, ce dernier est supprim=E9.


Comment contourner le probl=E8me ?
Voyez-vous une autre m=E9thode pour faire cel=E0 sans avoir de fonction d'i=
nitialisation de la librairie ?

Merci


--
sn00py

5 réponses

1 2
Avatar
kanze
Gabriel Dos Reis wrote:
James Kanze writes:

[...]

| > C'est bien garanti par la norme ça ?

| Pas vraiment. Il y a deux questions ici vis-à-vis de la norme.

| -- La première, c'est que la norme donne deux alternatifs pour
| l'initialisation des statiques : ou bien, ils sont
| initialisés avant d'entrer en main, ou bien, ils sont
| initialisés avant la première utilisation de l'unité de
| compilation qui les contient.

| En fait, toutes les implémentations utilisent le permier, et
| pour deux raisons. D'abord, il y a de plus en plus de code
| qu'y compte, et qu'on ne veut pas casser, et deuxièmement,
| le deuxième alternatif n'est pas implémentable s'il y a des
| cycles (et la norme dit que si on le choisit, il faut qu'il
| marche -- sans exception pour des cycles).

| Dans la pratique, je crois qu'on peut dire que dans
| l'absence des objets linkés dynamiquement, les statiques
| sont initialisés avant l'entrée dans main.

Sauf que dans la pratique, il y a de plus en plus d'objets
dynamiquement liés.


Un peu trop, même, à mon avis:-). Reste que la norme exige que
l'édition de liens ait lieu avant l'exécution. Quand tu fais
l'édition de liens dynamiques, tu es en dehors de la norme
(actuel). (Sauf, éventuellement, si l'édition de liens
dynamiques a lieu automatiquement, avant d'entrer en main. En
tout cas, selon la norme, tu n'as un « exécutable » qu'après
tout est lié. Et sans exécutable, pas d'exécution.)

De toute façon, aucune implémentation des objets dynamiquement
liés implémente correctement la deuxième phrase de §3.6/3, qui
exigerait un tri topologique de l'objet dynamique (et qui sera
impossible dans le cas des cycles de dépendance).

Dans l'ensemble, l'édition de liens dynamique est une autre
question, qu'il faut bien prendre en compte. Ou non, selon les
exigeances de l'application. Mais je crois que tu as raison ;
j'aurais dû au moins l'évoquer. Techniquement, il ne fait pas
partie du C++, et il ne s'agit pas non plus des bibliothèques,
mais pratiquement, c'est effectivement possible que le poster
doit le prendre en compte, quel que soit sa position vis-à-vis
de la norme, et indifférement de comment on l'appelle. Même s'il
n'intervenait pas dans son exemple.

Dans la pratique, je crois qu'on peut dire que les constructeurs
des objets statiques dans un objet dynamique sont appelés quand
l'objet est chargé : pendant l'appel à dlopen ou son équivalent
sous Windows. Dans un ordre indéfini, sauf pour les objets à
l'intérieur d'une même unité de compilation. (Maintenant, tu vas
me dire que j'ai oublié la question des threads, et ce qui se
passe si deux threads différents appellent dlopen en
parallel:-).)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Gabriel Dos Reis
writes:

[...]

| Dans la pratique, je crois qu'on peut dire que les constructeurs
| des objets statiques dans un objet dynamique sont appelés quand
| l'objet est chargé : pendant l'appel à dlopen ou son équivalent
| sous Windows. Dans un ordre indéfini, sauf pour les objets à
| l'intérieur d'une même unité de compilation. (Maintenant, tu vas
| me dire que j'ai oublié la question des threads, et ce qui se
| passe si deux threads différents appellent dlopen en
| parallel:-).)

Exact (pour tout :-))

-- Gaby
Avatar
plouf
...
En fait, toutes les implémentations utilisent le permier, et
pour deux raisons. D'abord, il y a de plus en plus de code
qu'y compte, et qu'on ne veut pas casser, et deuxièmement,
le deuxième alternatif n'est pas implémentable s'il y a des
cycles (et la norme dit que si on le choisit, il faut qu'il
marche -- sans exception pour des cycles).

Dans la pratique, je crois qu'on peut dire que dans
l'absence des objets linkés dynamiquement, les statiques
sont initialisés avant l'entrée dans main.


C'est ce qui m'interesse. Donc ça va.


-- La norme ne s'adresse pas à la question : comment invoquer
le compilateur, et donc, comment spécifier qu'une source (ou
l'objet qui en dérive) fasse partie du programme. Dans les
....

Ils ont aussi des variations sur des possibilités
supplémentaires. Mais la distinction fichier objet/fichier
bibliothèque est tellement universelle que je crois qu'on
peut y compter.
(surtout l'ordre) qu'ils traitent les différents éléments.
Ils ont aussi des variations sur des possibilités
supplémentaires. Mais la distinction fichier objet/fichier
bibliothèque est tellement universelle que je crois qu'on
peut y compter.


Je ne pensais pas que la tradition était aussi importante.
J'aurais cru que les norme étaient plus précise. C'est bon
à savoir.

C'est assez étonnant. Peut-être que la norme incorporera un
jour ces éléments. Puisque celà semble être un standard
de fait ?


Donc, si ta question est : est-ce qu'il est garanti par la norme
que, donné les sources ci-dessus, les commandes :

g++ -g -c -Wall libtoto.cpp
g++ -g -c -Wall main.cpp
g++ -g -Wall main.o libtoto.o -o tototest


génèreront un programme qui affiche "toto is used", la résponse
est certainement non -- ces commandes-là, elles ne feront
absolument rien avec VC++, par exemple.
D'accord, j'ai pris gcc comme exemple car c'est assez répandu :-D


Mais, et je crois que
c'est ce qui t'intéresse, tu peux compter que pour tout
compilateur C++, il y a une suite équivalente des commandes, et
que cette suite équivalente donnera bien un programme qui
affiche "toto is used".
Merci beaucoup pour toutes ces précisions.



même s'il n'y a rien dans le 'main' qui indique que l'objet
toto est utilisé ?


La question n'est pas si toto est utilisé. La question, c'est
d'abord et surtout si libtoto.cpp fait partie de ton programme.
Si libtoto.cpp fait partie du programme, l'objet qu'il contient
doit être initialisé.

J'ai bien saisi la différence fichier objet/Bibliotheque,

j'avais tendance à ne pas trop la faire avant. C'est vrai
que dans beaucoup de cas ça ne change rien au fonctionnement
du programme.


Par contre j'ai fait un programme qui utilise assez massivement
l'initialisation d'objets qui ne sont pas utilisés dans le 'main'
pour faire de choses avant l'execution du 'main' et ça m'aurait
vraiment embêter d'apprendre que ça ne marche qu'avec gcc sous
linux (par exemple).

C'est parfois très pratique ce comportement.


Avatar
plouf
Dans l'ensemble, l'édition de liens dynamique est une autre
question, qu'il faut bien prendre en compte. Ou non, selon les
exigeances de l'application. Mais je crois que tu as raison ;
j'aurais dû au moins l'évoquer. Techniquement, il ne fait pas
partie du C++, et il ne s'agit pas non plus des bibliothèques,
mais pratiquement, c'est effectivement possible que le poster
doit le prendre en compte, quel que soit sa position vis-à-vis
de la norme, et indifférement de comment on l'appelle. Même s'il
n'intervenait pas dans son exemple.


J'ai essayé de faire l'exemple le plus court possible mais en effet
j'utilise aussi la même chose avec des objets dynamiquement liés.

J'aurais pu le mettre finalement, ça n'aurait pas été beaucoup
plus long.

Avatar
kanze
plouf wrote:

[...]
Je ne pensais pas que la tradition était aussi importante.
J'aurais cru que les norme étaient plus précise. C'est bon à
savoir.


La norme a précédance sur la tradition. Si c'est écrit X dans la
norme, un compilateur doit faire X, même si on a toujours fait Y
dans la passée. Néaumoins :

-- La norme se base sur un vocabulaire établi. À un certain
point, par exemple, il parle de compilateurs, ou de
fichiers, ou de caractères, en supposant qu'on sait à peu
près ce que signifient ces mots. (Ce qui n'est pas toujours
évident.)

-- La norme fait exprès de laisser ouvert des questions du
genre « comment invoquer le compilateurs ». Parce que les
traditions sont fort différentes entre les différents
systèmes.

C'est assez étonnant. Peut-être que la norme incorporera un
jour ces éléments. Puisque celà semble être un standard de
fait ?


Quels éléments ? En fin de compte, il s'agit ici comment appeler
le compilateur, ou au moins, comment spécifier quelles unités
font partie d'un programme donné. Et s'il y a assez de consensus
sur la signification du mot « bibliothèque » dans ce cas, et sur
le fait de spécifier une bibliothèque à l'éditeur de liens ne
spécifie pas l'inclusion de toutes les unités dans la
bibliothèque, il reste énormement de différences dans les
détails. Donc, par exemple, si tu spécifies trois bibliothèques
à l'éditeur de liens de Microsoft, il va les concaténer, et les
traiter ensemble (plus ou moins). L'éditeur de liens de Unix, en
revanche, va les traiter dans l'ordre.

Donc, si ta question est : est-ce qu'il est garanti par la
norme que, donné les sources ci-dessus, les commandes :

g++ -g -c -Wall libtoto.cpp
g++ -g -c -Wall main.cpp
g++ -g -Wall main.o libtoto.o -o tototest


génèreront un programme qui affiche "toto is used", la
résponse est certainement non -- ces commandes-là, elles ne
feront absolument rien avec VC++, par exemple.


D'accord, j'ai pris gcc comme exemple car c'est assez répandu
:-D


Tout à fait. C'était juste pour essayer à faire comprendre
pourquoi la norme ne s'y mêle pas.

Mais, et je crois que c'est ce qui t'intéresse, tu peux
compter que pour tout compilateur C++, il y a une suite
équivalente des commandes, et que cette suite équivalente
donnera bien un programme qui affiche "toto is used".


Merci beaucoup pour toutes ces précisions.

même s'il n'y a rien dans le 'main' qui indique que
l'objet toto est utilisé ?


La question n'est pas si toto est utilisé. La question,
c'est d'abord et surtout si libtoto.cpp fait partie de ton
programme. Si libtoto.cpp fait partie du programme, l'objet
qu'il contient doit être initialisé.


J'ai bien saisi la différence fichier objet/Bibliotheque,
j'avais tendance à ne pas trop la faire avant. C'est vrai que
dans beaucoup de cas ça ne change rien au fonctionnement du
programme.


Mais si. Ou au moins, sinon le fonctionnement, au moins la
taille. Il y a littéralement des gigaoctets de code dans les
bibliothèques sur ma machine. Je n'ai vraiment pas envie à ce
que le moindre petit programme hello world les contient tous.

Dans certains cas, c'est même plus important. Dans ma
bibliothèque de mise au point, j'ai des fonctions ::operator new
et ::operator delete. Si j'utilise cette bibliothèque, ces
symboles sont donc définis. Mais ils sont aussi définis dans la
bibliothèque de base de C++. Si l'éditeur de liens incorporait
systèmatiquement toutes les unités de la bibliothèque, j'aurais
une double définition.

Par contre j'ai fait un programme qui utilise assez
massivement l'initialisation d'objets qui ne sont pas utilisés
dans le 'main' pour faire de choses avant l'execution du
'main' et ça m'aurait vraiment embêter d'apprendre que ça ne
marche qu'avec gcc sous linux (par exemple).

C'est parfois très pratique ce comportement.


Je ne crois pas. Je crois qu'il n'y a vraiment pas de cas où tu
voudrais vraiment que l'éditeur de liens incorpore toutes les
unités de toutes les bibliothèques qu'il voit. Ne serait-ce qu'à
cause de la taille des exécutables qui en résultera.

Ce qui peut être utile, c'est la possibilité de dire à l'éditeur
de liens que tu veux incorporer toutes les unités d'une
bibliothèque donnée. Mais comment tu dis quoique ce soit à
l'éditeur de liens n'est pas dans la portée de la norme. Ce
n'est donc pas de ce côté-là qu'il faut chercher. Et
malheureusement, aucun des éditeurs de liens que je connais en a
cette option.

Sous Unix, tu as la possibilité d'écrire quelque chose du
genre :
tmpDir=/tmp/$$ ;
ici=` pwd ` ;
(cd $tmpDir ; ar -x $ici/libMine.a) ;
$(LINK) ... $tmpDir/*.o ... ;
rm -r $tmpDir
dans le makefile. Sous Windows, il doit aussi avoir des
possibilités d'extraire tous les fichiers d'une bibliothèque, de
les mettre dans un répertoire à part, et de spécifier tous les
fichiers objet dans ce répertore au compilateur.

C'est une solution qui me convient assez pour mes projets à moi.
Mais c'est une complication de plus pour l'utilisateur. Si je
dois livrer à un autre, dans la mesure du possible,
j'utiliserais la solution dont j'ai parlé avant : je mettrais
des symboles publiques dans chaque unité concernée, j'utiliserai
des scripts pour générer une table avec tous les références
externes, et je prendrais l'adresse de cette table dans une
fonction que l'utilisateur est sûr d'appeler.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



1 2