OVH Cloud OVH Cloud

Déclaration de chaînes constantes

43 réponses
Avatar
Olivier Boudeville
Bonjour à tous,

je suis en train de développer une bibliothèque C++, et je rencontre des
problèmes à l'exécution avec certains couples machines-compilateurs
(plantages de toute la batterie de tests unitaires sur différents objets
STL) qui me font douter de certains usages C++ concernant la définition
de chaînes constantes (même si pourtant cela a l'air simple, ou devrait
l'être !).

Dans un fichier CeylanUniformResourceIdentifier.h je déclare :

namespace Ceylan
{
namespace URI
{
[..]
extern const std::string ProtocolSeparator ;
[..]

et dans CeylanUniformResourceIdentifier.cc (un des fichiers qui inclut
CeylanUniformResourceIdentifier.h) je le définis ainsi :

const std::string Ceylan::URI::ProtocolSeparator = "://" ;

Est-ce que c'est une pratique correcte ?

J'ai un doute car ProtocolSeparator est un objet qui serait à construire
avant la première instruction de main (licite ? dangereux ?), et
valgrind me sort :

"""
==30157== Invalid write of size 4
==30157== at 0x40D3165: _GLOBAL__I__ZN6Ceylan3URI17ProtocolSeparatorE
(CeylanTypes.h:432)
==30157== by 0x40D3324: (within
/home/sye/tmp-Ceylan-test-install/lib/libCeylan-0.3.so.0.0.3)
==30157== by 0x40524C8: (within
/home/sye/tmp-Ceylan-test-install/lib/libCeylan-0.3.so.0.0.3)
==30157== by 0x400B70C: call_init (in /lib/ld-2.3.6.so)
==30157== by 0x400B7F1: _dl_init (in /lib/ld-2.3.6.so)
==30157== by 0x400081E: (within /lib/ld-2.3.6.so)
==30157== Address 0xBEFFCBD8 is not stack'd, malloc'd or (recently) free'd
"""

_GLOBAL__I__ZN6Ceylan3URI17ProtocolSeparatorE décrypté donne : "global
constructors keyed to Ceylan::URI::ProtocolSeparator". Mon doute est
accentué par le fait que la référence mentionnée (CeylanTypes.h:432) n'a
strictement aucun rapport avec ProtocolSeparator et pointe sur :
"""
typedef std::list<Ceylan::Uint16>::size_type ListSize ;
"""

(c'est un raccourci d'écriture [et peut-être aussi une mauvaise
pratique] pour fournir au développeur un type plus simple à manier).

Je sais qu'il serait possible d'encapsuler chacune de ces chaînes
constantes par une fonction spécifique qui la retourne (ex :

const std::string& ProtocolSeparator()
{
static const std::string instance = "://";
return instance;
}

) mais je me passerais bien d'une telle encapsulation si elle est
facultative.

Vous serait-il possible de m'indiquer si d'emblée, purement sur le plan
du langage C++, ces formes sont correctes ? (après je sens que je vais
être bon pour continuer sur les listes de gcc et de valgrind)

Merci d'avance !

Olivier.

10 réponses

1 2 3 4 5
Avatar
Fabien LE LEZ
On Sat, 24 Jun 2006 12:33:05 +0200, Olivier Boudeville
:

namespace Ceylan
{
namespace URI
{
[..]
extern const std::string ProtocolSeparator ;
[..]

et dans CeylanUniformResourceIdentifier.cc (un des fichiers qui inclut
CeylanUniformResourceIdentifier.h) je le définis ainsi :

const std::string Ceylan::URI::ProtocolSeparator = "://" ;


J'ai un petit doute.
En tout cas, ceci est correct :

namespace Ceylan
{
namespace URI
{
const std::string ProtocolSeparator= "://";

est un objet qui serait à construire
avant la première instruction de main


C'est assez courant.

Avatar
Olivier Boudeville
Merci à Fabien et à toi pour vos réponses.

Si je n'utilise pas cette variable "ProtocolSeparator" en argument d'une
autre construction de type classe-statique ou extern, i.e. si, a priori,
sa première utilisation est à l'intérieur de main(), alors (je m'étais
dit que) on est assuré que toutes les constructions situées en dehors de
toute fonction seraient achevées à l'entrée dans main().

Si cela n'était pas le cas, ma question se résume à : comment diable, en
C++, déclarer une chaîne de caractère (constante) dans un header, en ne
la définissant qu'une seule fois, à effort minimal (i.e. si possible
sans encapsulation dans une fonction), si possible en ne faisant pas
figurer sa valeur dans le header ? (cette dernière propriété m'importe
moins que toutes les autres néanmoins).

Damien : c'est suite aux premiers crashs que je suis passé de la forme
originelle

"""
namespace Ceylan
{
namespace URI
{
const std::string ProtocolSeparator= "://";
"""


à

"""
namespace Ceylan
{
namespace URI
{
[..]
extern const std::string ProtocolSeparator ;
[..]
"""

en pensant (à tort ?) avoir défini, avec cette première forme, le
symbole ProtocolSeparator autant de fois que ce header était inclus dans
un fichier d'implémentation.

Malheureusement cela n'a pas résolu mes crashs, et je suis resté dans la
deuxième forme, par "inertie" et parce que j'imagine que moins on expose
d'informations dans l'API, moins on risque de devoir la changer.

Merci d'avance pour tout conseil,

Olivier.



Olivier Boudeville writes:

| Bonjour à tous,
|
| je suis en train de développer une bibliothèque C++, et je rencontre des
| problèmes à l'exécution avec certains couples machines-compilateurs
| (plantages de toute la batterie de tests unitaires sur différents objets
| STL) qui me font douter de certains usages C++ concernant la définition
| de chaînes constantes (même si pourtant cela a l'air simple, ou devrait
| l'être !).
|
| Dans un fichier CeylanUniformResourceIdentifier.h je déclare :
|
| namespace Ceylan
| {
| namespace URI
| {
| [..]
| extern const std::string ProtocolSeparator ;
| [..]
|
| et dans CeylanUniformResourceIdentifier.cc (un des fichiers qui inclut
| CeylanUniformResourceIdentifier.h) je le définis ainsi :
|
| const std::string Ceylan::URI::ProtocolSeparator = "://" ;

Comment t'assures-tu qu'il est effectivement initialisé avant sa
première utilisation ?

-- Gaby


Avatar
Gabriel Dos Reis
Olivier Boudeville writes:

| Bonjour à tous,
|
| je suis en train de développer une bibliothèque C++, et je rencontre des
| problèmes à l'exécution avec certains couples machines-compilateurs
| (plantages de toute la batterie de tests unitaires sur différents objets
| STL) qui me font douter de certains usages C++ concernant la définition
| de chaînes constantes (même si pourtant cela a l'air simple, ou devrait
| l'être !).
|
| Dans un fichier CeylanUniformResourceIdentifier.h je déclare :
|
| namespace Ceylan
| {
| namespace URI
| {
| [..]
| extern const std::string ProtocolSeparator ;
| [..]
|
| et dans CeylanUniformResourceIdentifier.cc (un des fichiers qui inclut
| CeylanUniformResourceIdentifier.h) je le définis ainsi :
|
| const std::string Ceylan::URI::ProtocolSeparator = "://" ;

Comment t'assures-tu qu'il est effectivement initialisé avant sa
première utilisation ?

-- Gaby
Avatar
Fabien LE LEZ
On Sat, 24 Jun 2006 21:05:18 +0200, Olivier Boudeville
:

Si je n'utilise pas cette variable "ProtocolSeparator" en argument d'une
autre construction de type classe-statique ou extern, i.e. si, a priori,
sa première utilisation est à l'intérieur de main(), alors (je m'étais
dit que) on est assuré que toutes les constructions situées en dehors de
toute fonction seraient achevées à l'entrée dans main().


Si je ne m'abuse, c'est bien le cas.
Le problème étant de s'assurer qu'aucun code n'utilise cette variable
avant le début de main().

déclarer une chaîne de caractère (constante) dans un header, [...]


En gros, tu voudrais un #define sans les inconvénients de #define.

static std::string const ma_chaine ("://");
peut-être ?

si possible sans encapsulation dans une fonction),


Pourquoi cette condition ?

Le C++ se débrouille assez mal avec les variables globales, et assez
bien avec les fonctions.

Quand je suis dans le cas que tu décris, j'utilise une fonction. Ça
peut te paraître bourrin, mais ça marche.

et parce que j'imagine que moins on expose
d'informations dans l'API, moins on risque de devoir la changer.


Justement, exposer directement une variable fait partie des
informations qu'on expose alors qu'on risque de devoir les changer un
jour.

Avatar
Sylvain
Fabien LE LEZ wrote on 24/06/2006 21:31:
On Sat, 24 Jun 2006 21:05:18 +0200, Olivier Boudeville
:

Si je n'utilise pas cette variable "ProtocolSeparator" en argument d'une
autre construction de type classe-statique ou extern, i.e. si, a priori,
sa première utilisation est à l'intérieur de main(), alors (je m'étais
dit que) on est assuré que toutes les constructions situées en dehors de
toute fonction seraient achevées à l'entrée dans main().


Si je ne m'abuse, c'est bien le cas.
Le problème étant de s'assurer qu'aucun code n'utilise cette variable
avant le début de main().


c'est le cas individuel (chaque object static est construit à l'entrée
de main) mais il n'y a pas de règle d'ensemble: chacun de ces objects
statiques est contruit dans un ordre imprévisible.

il peut donc y avoir crash si un objet static s'initialise à partir
d'autres d'objects static (pouvant ou non être alors construits).

je suppose que ProtocolSeparator est utilisé par d'autre cst statique
pour créer d'autre URI; si c'est le cas, sa substitution par une
fonction est requise - et je rejoins l'avis de Fabien sur ce point, la
fonction n'a pas de lourdeur intrinsèque et garantira l'initialisation
de ton object.

Sylvain.


Avatar
Jean-Marc Bourguet
Fabien LE LEZ writes:

static std::string const ma_chaine ("://");


const implique static si il n'y a pas une déclaration
antérieure qui indique que c'est avec un "linkage" externe.

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
Jean-Marc Bourguet
Sylvain writes:

Si je ne m'abuse, c'est bien le cas. Le problème étant
de s'assurer qu'aucun code n'utilise cette variable
avant le début de main().


c'est le cas individuel (chaque object static est
construit à l'entrée de main)


Ca dépend de l'implémentation. La seule contrainte c'est que
si elles sont postposées après le début de l'exécution de
main, elles doivent avoir lieu avant la première exécution
d'une fonction définie dans leur unité de compilation.
(L'idée est de permettre de postposer le chargement d'une
bibliothèque dynamique à sa première utilisation.)

mais il n'y a pas de règle d'ensemble: chacun de ces objects
statiques est contruit dans un ordre imprévisible.


Il faut discerner les initialisations avec des valeurs
constantes (appelées initialisations statiques) et les
autres (appelées initialisations dynamiques).

L'initialisation des variables statiques se fait en trois
temps:

- d'abord une initialisation à 0 (qui n'est pas un
memset(&v, 0, sizeof v) mais un v = 0).

- ensuite toutes les initialisation statiques (en pratique
ces deux phases ont lieu en même temps, un programme
conforme ne pouvant pas voir la différence).

- ensuite les initialisations dynamiques. A l'intérieur
d'une unité de compilation, elles se font dans l'ordre
d'apparition des définitions.

Donc il y a donc trois ordres prévisibles (les
initialisations statiques sont faites avant toutes
initialisations dynamiques, à l'intérieur d'une unité de
compilation les initialisations dynamiques sont ordonnées,
on sait que les objets ayant une initialisation dynamique
seront au moins initialisés à 0).

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
Sylvain
Jean-Marc Bourguet wrote on 24/06/2006 22:14:

Donc il y a donc trois ordres prévisibles (les


entièrement d'accord avec tout ça, mais ...

je m'autorise à penser que le programme est ici multi-unités (ou la
nécessité d'export de ProtocolSeparator dans un header sera suspecte) or
l'ordre d'initialisation des unités est (généralement, parfois, souvent,
rayer les mentions inutiles) indéfini.

Sylvain.

Avatar
Jean-Marc Bourguet
Olivier Boudeville writes:

Si je n'utilise pas cette variable "ProtocolSeparator" en
argument d'une autre construction de type classe-statique
ou extern, i.e. si, a priori, sa première utilisation est
à l'intérieur de main(), alors (je m'étais dit que) on est
assuré que toutes les constructions situées en dehors de
toute fonction seraient achevées à l'entrée dans main().


Ce n'est pas garanti, voir ma réponse à Sylvain. Mais en
pratique, je ne suis pas sûr que la latitude laissée par la
norme soit utilisée (alors que j'ai essayé, je ne l'ai pas
observé sauf en utilisant une bibliothèque chargée
explicitement avec dlopen ou équivalent).

Si cela n'était pas le cas, ma question se résume à :
comment diable, en C++, déclarer une chaîne de caractère
(constante) dans un header, en ne la définissant qu'une
seule fois, à effort minimal (i.e. si possible sans
encapsulation dans une fonction), si possible en ne
faisant pas figurer sa valeur dans le header ? (cette
dernière propriété m'importe moins que toutes les autres
néanmoins).


extern const std::string ProtocolSeparator;

dans un en-tête et

const std::string ProtocolSeparator("://");

dans une seule unité de compilation devrait fonctionner si
il n'y a pas d'utilisation avant l'exécution de main (sauf
utilisation de la latitude que j'ai expliquée dans la
réponse à Sylvain mais que je n'ai jamais observée, est-ce
que tu as des bibliothèques dynamiques?)

Damien : c'est suite aux premiers crashs que je suis passé
de la forme originelle

"""
namespace Ceylan
{
namespace URI
{
const std::string ProtocolSeparator= "://";


Ceci déclare une variable à linkage interne (comme s'il y
avait un static). Ca devrait donc fonctionner même si le
système utilise la possibilité de retarder l'intialisation
de variables à durée de vie statique après le début de main.
Si tu as des crash avec, j'ai l'impression qu'il faut
chercher le problème ailleurs

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
Olivier Boudeville
Merci à tous pour vos réponses,

je déduis de tout cela que les trois formes :

- 'const std::string s = "foo";' dans le header
- 'extern const std::string s ;' dans le header + "const std::string s
= "foo" ;' dans l'implémentation
- 'const std::string & get_s() ;' + sa définition

sont correctes, et que la troisième est probablement à privilégier en
général. La seconde est peut-être surtout intéressante dans les cas où
il a beaucoup de constantes à déclarer, pour s'épargner du code très
long ou l'usage de macros et/ou si on a un grand besoin de performance à
ce niveau (usage fréquent de ces variables).

Les deux premières doivent être maniées avec prudence pour ne pas créer
de références entre instances créées avant main, dans la mesure où leur
ordre de création n'est que partiellement spécifié par la norme.

Les deux dernières permettent une meilleure encapsulation.

Maintenant que j'ai disculpé un élément, je retourne à mon débogage (qui
en effet ne se bornera vraisemblablement pas à des déclarations à mettre
à jour, hélas).

Merci en tout cas pour votre aide !

Olivier.


Sylvain writes:

[...]

| c'est le cas individuel (chaque object static est construit à l'entrée
| de main) mais il n'y a pas de règle d'ensemble: chacun de ces objects
| statiques est contruit dans un ordre imprévisible.

Pas exactement. L'ordre, dans une unité de traduction donnée, est spéifié.

-- Gaby


1 2 3 4 5