Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Precision sur la "one definition rule"

14 réponses
Avatar
Aurelien Regat-Barrel
Bonjour,
J'ai eu affaire à un drôle de bug dans mon programme, provoqué par un
code de ce genre:

// montemplate.h
template<typename T>
class montemplate
{
#ifdef MODIF
// quelque chose
#else
// autre chose
#endif
};

// test.cpp
#define MODIF
#include "montemplate.h"

// utilisation de montemplate...


// main.cpp
#include "montemplate.h"

// utilisation de montemplate...


Je pensais pas que ce genre de code était problématique (unités de
compilations différentes), mais apparement si (dixit la one definition
rule).
Vous confirmez ?
Merci.

--
Aurélien Regat-Barrel

10 réponses

1 2
Avatar
Jean-Marc Bourguet
Aurelien Regat-Barrel writes:

Je pensais pas que ce genre de code était problématique (unités de
compilations différentes), mais apparement si (dixit la one definition
rule).
Vous confirmez ?


Oui. Ca peut ou pas causer des problemes suivant les modifications,
mais c'est a chaque fois du comportement indetermine. Pense par
exemple a

template <typename T>
class c {
public:
~c() {
#ifdef MODIF
delete[] m_;
#endif
}
private:
#ifdef MODIF
char* m_;
#endif
int k;
};

et que tu liberes dans l'unite avec MODIF un exemplaire alloue dans
une unite sans MODIF.

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Rémy
"Aurelien Regat-Barrel" a écrit dans le message
de news: 441ae3d7$0$31419$
Bonjour,
J'ai eu affaire à un drôle de bug dans mon programme, provoqué par un code
de ce genre:

// montemplate.h
template<typename T>
class montemplate
{
#ifdef MODIF
// quelque chose
#else
// autre chose
#endif
};

// test.cpp
#define MODIF
#include "montemplate.h"

// utilisation de montemplate...


// main.cpp
#include "montemplate.h"

// utilisation de montemplate...


Je pensais pas que ce genre de code était problématique (unités de
compilations différentes), mais apparement si (dixit la one definition
rule).
Vous confirmez ?
Merci.



Eh bien si "quelque chose" et "autre chose" sont suffisamment différents, il
y aura forcément un problème à l'édition de liens (lorsqu'on essaye de lier
ensemble test.o et main.o).

Avatar
Aurelien Regat-Barrel
Oui. Ca peut ou pas causer des problemes suivant les modifications,
mais c'est a chaque fois du comportement indetermine. Pense par
exemple a

template <typename T>
class c {
public:
~c() {
#ifdef MODIF
delete[] m_;
#endif
}
private:
#ifdef MODIF
char* m_;
#endif
int k;
};

et que tu liberes dans l'unite avec MODIF un exemplaire alloue dans
une unite sans MODIF.


Ca je comprends tout à fait. Là où je suis davantage surpris c'est si
les 2 unités ont chacune leur propre "version" et qu'elles ne se
mélangent pas.


--
Aurélien Regat-Barrel

Avatar
Aurelien Regat-Barrel
Eh bien si "quelque chose" et "autre chose" sont suffisamment différents, il
y aura forcément un problème à l'édition de liens (lorsqu'on essaye de lier
ensemble test.o et main.o).


Le problème est effectivement survenu à l'édition de liens, mais pas de
manière systématique (avec VC++ 8). En DEBUG pas de problème, en RELEASE
sans optimisation globale non plus. Avec optimisation globale (/GL), ça
crée des problèmes.
Pour info, j'ai eu ce problème "à l'insu de mon plein grès" avec
std::vector sous VC++ 8 en définissant _SECURE_SCL à 0 dans 1 fichier de
mon projet pour éviter d'avoir des warnings (ailleurs que dans
std::vector). Sauf que ça modifie pas mal de choses dans std::vector
aussi, et voilà.

--
Aurélien Regat-Barrel

Avatar
Jean-Marc Bourguet
Aurelien Regat-Barrel writes:

Oui. Ca peut ou pas causer des problemes suivant les modifications,
mais c'est a chaque fois du comportement indetermine. Pense par
exemple a
template <typename T>
class c {
public:
~c() { #ifdef MODIF
delete[] m_;
#endif
}
private:
#ifdef MODIF
char* m_;
#endif
int k;
};
et que tu liberes dans l'unite avec MODIF un exemplaire alloue dans
une unite sans MODIF.


Ca je comprends tout à fait. Là où je suis davantage surpris c'est si les 2
unités ont chacune leur propre "version" et qu'elles ne se mélangent pas.


En es-tu sur qu'il n'y a pas de melange (du genre partage de code)?

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
kanze
Aurelien Regat-Barrel wrote:

Eh bien si "quelque chose" et "autre chose" sont
suffisamment différents, il y aura forcément un problème à
l'édition de liens (lorsqu'on essaye de lier ensemble test.o
et main.o).



Comme disait Jean-Marc, s'il y a la moindre différence, c'est un
comportement indéfini. La norme existe que le compilateur voit
exactement la même séquence de tokens dans toutes les unités de
traduction.

Dans la pratique, tout dépend. En général, tu peux bien utiliser
assert, par exemple, même si dans une unité de traduction,
NDEBUG est défini, et pas dans les autres. C'est un comportement
indéfini, mais dans la pratique, je vois mal le cas où il pose
un problème (sauf dans le cas où le compilateur vérifie les
violations de la ODR -- et il me semble que David Vandevoorte a
dit une fois que le front-end EDG en a une option pour le faire).

Le problème est effectivement survenu à l'édition de liens,


Sois content. Moi, aussi, je l'ai rencontré, mais seulement à
l'exécution, quand le programme s'est mis à faire des cores
dumps inexplicables.

mais pas de manière systématique (avec VC++ 8). En DEBUG pas
de problème, en RELEASE sans optimisation globale non plus.
Avec optimisation globale (/GL), ça crée des problèmes.

Pour info, j'ai eu ce problème "à l'insu de mon plein grès"
avec std::vector sous VC++ 8 en définissant _SECURE_SCL à 0
dans 1 fichier de mon projet pour éviter d'avoir des warnings
(ailleurs que dans std::vector). Sauf que ça modifie pas mal
de choses dans std::vector aussi, et voilà.


_SECURE_SCL, ça ne serait pas un peu comme _GLIBCXX_DEBUG sous
G++ ? C'était exactement mon problème, sauf que ça linkait bien.
(Dans mon cas, c'était une erreur dans le fichier de make.
J'étais convaincu que je me servais des bibliothèques de debug,
alors que ce n'était pas le cas. J'avais un vecteur qui était
créer dans la bibliothèque, mais dont je me servais des
itérateurs dans une fonction templatée instantiée dans main.)

En régle générale : il faut que toutes les unités de compilation
soient compilées avec exactement les mêmes options, y compris
les mêmes -D. Et que tu ne définisses jamais de macro dans ton
code avant d'inclure les en-têtes, et que les en-têtes aussi ne
définissent que les macros qui les concernent.

Plus ou moins. Il y a des options qui spécifient les fichiers de
sortie, par exemple, et là, évidemment, il vaut mieux qu'elles
soient différentes d'une unité de compilation à l'autre:-). Des
variations dans le niveau d'avertissements ne doivent pas poser
de problèmes non plus. (Avec VC++, si tu fais cl /help, les
options dans « -OUTPUT FILES- », et quelques unes dans
« -MISCELLANEOUS- », vont bien. Les autres non.)

Il faut aussi se rappeler que certains compilateurs peuvent
instantier les templates lors de l'édition des liens, et que
dans ce cas-là, il faut passer aussi toutes les options à
l'édition de liens aussi. De même, certains compilateurs (Sun
CC, par exemple, mais probablement d'autres qui se servent d'un
repositoire) peuvent instantier les templates lors de la
génération des bibliothèques -- dans ce cas-là, il faut aussi
les mêmes options alors.

--
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
Aurelien Regat-Barrel
Ca je comprends tout à fait. Là où je suis davantage surpris c'est si les 2
unités ont chacune leur propre "version" et qu'elles ne se mélangent pas.



En es-tu sur qu'il n'y a pas de melange (du genre partage de code)?


Le problème surgit avec l'option d'optimisation globale (/GL) qui en
gros compile tout les différents fichiers sources comme si c'était un
seul fichier unique.
Je cherche à savoir si cette erreur est une conséquence de cette
optimisation globale, ou si c'est juste la manifestation d'un problème
qui relève bien du code. Cette histoire de template instanciée de
manière identique à l'ensemble des codes sources compilés, ça fait un
peu effet de bord je trouve...

--
Aurélien Regat-Barrel


Avatar
Aurelien Regat-Barrel

Le problème est effectivement survenu à l'édition de liens,



Sois content. Moi, aussi, je l'ai rencontré, mais seulement à
l'exécution, quand le programme s'est mis à faire des cores
dumps inexplicables.


Oups pardon. Mon exe s'est bien linké sans problèmes, et j'ai bien eu un
comportement incompréhensible au runtime, puis des plantages biensûr.
J'ai voulu dire que c'est l'éditeur de liens qui s'est embrouillé dans
son travail.

mais pas de manière systématique (avec VC++ 8). En DEBUG pas
de problème, en RELEASE sans optimisation globale non plus.
Avec optimisation globale (/GL), ça crée des problèmes.



Pour info, j'ai eu ce problème "à l'insu de mon plein grès"
avec std::vector sous VC++ 8 en définissant _SECURE_SCL à 0
dans 1 fichier de mon projet pour éviter d'avoir des warnings
(ailleurs que dans std::vector). Sauf que ça modifie pas mal
de choses dans std::vector aussi, et voilà.



_SECURE_SCL, ça ne serait pas un peu comme _GLIBCXX_DEBUG sous
G++ ? C'était exactement mon problème, sauf que ça linkait bien.
(Dans mon cas, c'était une erreur dans le fichier de make.
J'étais convaincu que je me servais des bibliothèques de debug,
alors que ce n'était pas le cas. J'avais un vecteur qui était
créer dans la bibliothèque, mais dont je me servais des
itérateurs dans une fonction templatée instantiée dans main.)


Dans mon cas c'est un peu plus pervers : c'est la fonctions inline qui
manipulait un vector (std::max_element) renvoyé par une fonction non
inline qui provoquait le problème.

En régle générale : il faut que toutes les unités de compilation
soient compilées avec exactement les mêmes options, y compris
les mêmes -D. Et que tu ne définisses jamais de macro dans ton
code avant d'inclure les en-têtes, et que les en-têtes aussi ne
définissent que les macros qui les concernent.


J'ai enfreint ce principe dans un seul fichier de tout le projet, et
voilà :-)

Plus ou moins. Il y a des options qui spécifient les fichiers de
sortie, par exemple, et là, évidemment, il vaut mieux qu'elles
soient différentes d'une unité de compilation à l'autre:-). Des
variations dans le niveau d'avertissements ne doivent pas poser
de problèmes non plus. (Avec VC++, si tu fais cl /help, les
options dans « -OUTPUT FILES- », et quelques unes dans
« -MISCELLANEOUS- », vont bien. Les autres non.)

Il faut aussi se rappeler que certains compilateurs peuvent
instantier les templates lors de l'édition des liens, et que
dans ce cas-là, il faut passer aussi toutes les options à
l'édition de liens aussi. De même, certains compilateurs (Sun
CC, par exemple, mais probablement d'autres qui se servent d'un
repositoire) peuvent instantier les templates lors de la
génération des bibliothèques -- dans ce cas-là, il faut aussi
les mêmes options alors.


Là j'ai eu le problème avec l'option /GL qui fait que la compilation
n'est pas complète mais est terminée par le linker (qui rappelle le
compilo). Je suppose que dans cette configuration les templates sont
instanciés à l'édition de liens.
Aucun problème en fonctionnement "classique".

--
Aurélien Regat-Barrel


Avatar
Jean-Marc Bourguet
Aurelien Regat-Barrel writes:

Ca je comprends tout à fait. Là où je suis davantage surpris c'est si les 2
unités ont chacune leur propre "version" et qu'elles ne se mélangent pas.
En es-tu sur qu'il n'y a pas de melange (du genre partage de code)?



Le problème surgit avec l'option d'optimisation globale (/GL) qui en
gros compile tout les différents fichiers sources comme si c'était
un seul fichier unique.


Ca ne me semble pas etonnant que des problemes ne se relevent qu'avec
ce genre d'options.

Je cherche à savoir si cette erreur est une conséquence de cette
optimisation globale, ou si c'est juste la manifestation d'un
problème qui relève bien du code.


Si on joue le jeu formel, ca releve du code: tu n'as pas respecte une
des regles. Si tu veux comprendre pourquoi dans ton exemple
l'optimiseur agit comme il le fait, sans voir le code c'est difficile.
En general avec les optimiseurs, plus ils sont pousses, plus ils ont
l'air d'aller chercher des informations n'importe ou, parfois de
maniere surprenante.

Je crois que j'ai deja donne ce genre d'exemples (je ne l'ai pas vecu,
je ne sais plus si je l'ai vu comme etant le comportement d'un
optimiseur reel ou bien dans un contexte similaire de celui-ci ou
quelqu'un donnait un exemple de comportement possible d'un optimiseur
sans pretendre qu'il y en a un actuellement le faisant):

if (ptr == NULL && logging_activated) {
log << "Pointer is nulln";
}
ptr->f();

ou l'optimiseur considerant que ptr->f() n'est du comportement defini
que si ptr est non nul deduit que ptr est non null donc que le test
dans le if est une constante fausse et vire le if et la branche
loggant le fait que ptr est nul. Un probleme arrive et ptr est nul.
Pas d'info dans la trace et retrouver la cause du probleme est un peu
plus complique que si l'optimiseur avait ete moins malin.

Cette histoire de template instanciée de manière identique à
l'ensemble des codes sources compilés, ça fait un peu effet de bord
je trouve...


On pourrait desirer que le non respect de l'ODR soit detecte, mais a
part ca je vois mal ce que tu veux. Tu peux mettre la definition du
template dans un namespace anonyme, mais alors tu ne peux plus
passer tes objets d'une unite de compilation a l'autre.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org



Avatar
Aurelien Regat-Barrel
Cette histoire de template instanciée de manière identique à
l'ensemble des codes sources compilés, ça fait un peu effet de bord
je trouve...



On pourrait desirer que le non respect de l'ODR soit detecte, mais a
part ca je vois mal ce que tu veux. Tu peux mettre la definition du
template dans un namespace anonyme, mais alors tu ne peux plus
passer tes objets d'une unite de compilation a l'autre.


Je veux juste comprendre. J'ai fait une erreur, je cherche pas à ce
qu'elle soit acceptée. Par contre j'aurai bien aimé qu'elle soit détectée.
Pour te donner une idée de code qui crash sous VC++ 8 :

// test.cpp
#define _SECURE_SCL 0
#include <vector>
#include <algorithm>

void test()
{
std::vector<int> v( 150, 10 );
std::vector<int>::iterator max_i std::max_element( v.begin(), v.end() );
int max = *max_i;
}

// main.cpp
#include <vector>
template class std::vector<int, std::allocator< int > >;

void test();

int main()
{
test();
return 0;
}

Pas besoin de passer des instances de std::vector entre les unités de
compilation, la spécialisation explicite dans main.cpp suffit. Et si
c'est main.cpp qui définit _SECURE_SCL à 0, ça marche...

En fait _SECURE_SCL influence sur le type de l'itérateur. Je me demande
s'il serait possible de provoquer des erreurs à l'édition des liens si
l'ODR n'était pas respectée.

--
Aurélien Regat-Barrel


1 2