tableau constant de pointeurs constants C=KO C++=OK

Le
Lea GRIS
Bonjour,

Je suis tombée sur une bizarrerie/limitation de C qui semble avoir
évolué avec C++ mais pas pour C90 ni C99 :

Si je veux obtenir une structure de données constante ou un table
constante initialisée avec des pointeurs vers d'autres constantes au
moment de la compilation, la méthode pourtant explicite et logique
suivante ne fonctionne pas :

/* Les noms de fruits sont des chaînes constantes.
* Le tableau contient des pointeurs constants
* vers ces chaînes constantes.
*/
const char * fruits[3] = { "abricot", "banane", "cerise" );

/* Logiquement, ce tableau constant devrait contenir
* les pointeurs constants vers ces chaînes constantes
*/
const char * menus[5] = { fruits[0], fruits[1], fruits[0], fruits[2],
fruits[0] };

Mais mon compilateur GCC se plaint que l'élément initialisant n'est pas
constant :
"initializer element is not constant"

Mon autre ami G++ (compilateur C++) quant à lui, comprend parfaitement
cette construction à la compilation, et renseigne le tableau avec les
pointeurs vers les chaînes respectives.

Alternativement et non-intuitivement, une méthode qui fonctionne en C
pour ce cas précis consiste à écrire :

const char * menus[5] = [ "abricot", "banane", "abricot", "cerise",
"abricot" };

Dans sa grande bonté, le compilateur GCC détecte et regroupe les chaînes
identiques même si cela tient peut être plus du standard que de
l'altruisme du compilateur.

Est-ce que C est si limité pour la compilation de constantes à la
structure complexe, où il existe d'obscures syntaxes appropriées, que ce
monsieur C voudrait bien comprendre ?

Amicalement,

--
Léa Gris
Questions / Réponses high-tech
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Anthony Fleury
Le #1001374
Bonjour,


Bonjour,

Je suis tombée sur une bizarrerie/limitation de C qui semble avoir
évolué avec C++ mais pas pour C90 ni C99 :

Si je veux obtenir une structure de données constante ou un table
constante initialisée avec des pointeurs vers d'autres constantes au
moment de la compilation, la méthode pourtant explicite et logique
suivante ne fonctionne pas :

/* Les noms de fruits sont des chaînes constantes.
* Le tableau contient des pointeurs constants
* vers ces chaînes constantes.
*/
const char * fruits[3] = { "abricot", "banane", "cerise" );

/* Logiquement, ce tableau constant devrait contenir
* les pointeurs constants vers ces chaînes constantes
*/
const char * menus[5] = { fruits[0], fruits[1], fruits[0], fruits[2],
fruits[0] };

Mais mon compilateur GCC se plaint que l'élément initialisant n'est pas
constant :
"initializer element is not constant"


En effet, en C (contrairement à C++), lorsque la variable a une portée
globale (static storage duration), son initialiseur doit être une
constante (0, 1.0, 0L, 'a'...) ou un string litteral ("...") mais non la
valeur d'une variable constante.

Dans le même genre :

const int t = 0; /* OK, on a bien une constante */
const int t1 = t; /* KO, même si t est une constante */

int main(void) {
return 0;
}

Alternativement et non-intuitivement, une méthode qui fonctionne en C
pour ce cas précis consiste à écrire :

const char * menus[5] = [ "abricot", "banane", "abricot", "cerise",
"abricot" };


Normal par rapport à ce qui a été dit ci-dessus, on a bien ici des
string litterals.

Dans sa grande bonté, le compilateur GCC détecte et regroupe les chaînes
identiques même si cela tient peut être plus du standard que de
l'altruisme du compilateur.


Et en effet tout ceci est standard.

Est-ce que C est si limité pour la compilation de constantes à la
structure complexe, où il existe d'obscures syntaxes appropriées, que ce
monsieur C voudrait bien comprendre ?


C est surtout limité pour l'initialisation de variables dont la durée
est celle du programme (static). Il n'y a à ma connaissance aucun moyen
de contourner ceci, sauf à mettre la variable menu dans une fonction (et
sans le mot clé static bien sûr...) ou à s'amuser à initialiser tout
ceci avec le préprocesseur.

Anthony

Antoine Leca
Le #1001372
En news:4797c50b$0$858$, Lea GRIS va escriure:
Je suis tombée sur une bizarrerie/limitation de C qui semble avoir
évolué avec C++ mais pas pour C90 ni C99 :

Si je veux obtenir une structure de données constante [...]


Hummm, « constante » en C n'est probablement pas ce que tu attends.
*const* en C signifie quelque chose comme « Moi programmeur m'engage à ne
pas modifier les objets de ce type » ; certains ont proposé de baptiser le
spécificateur *readonly* à la place...


Si je veux obtenir une structure de données constante ou un table
constante initialisée avec des pointeurs vers d'autres constantes au
moment de la compilation, la méthode pourtant explicite et logique
suivante ne fonctionne pas :

/* Les noms de fruits sont des chaînes constantes.
* Le tableau contient des pointeurs constants
* vers ces chaînes constantes.
*/
const char * fruits[3] = { "abricot", "banane", "cerise" );


<EN PASSANT>
Non. Les pointeurs du tableau ne sont pas « constants », tu as parfaitement
le droit d'écrire ailleurs
fruits[1] = "pommes";

Pour avoir des « pointeurs constants », il faudrait écrire
const char * const fruits[] = { "abricot",

Et si tu fais cela, avec l'instruction
fruits[1] = "pommes";
GCC répond
lea.c:8: error: assignment of read-only location
:-)
</EN PASSANT>


/* Logiquement, ce tableau constant devrait contenir
* les pointeurs constants vers ces chaînes constantes
*/
const char * menus[5] = { fruits[0], fruits[1], fruits[0], fruits[2],
fruits[0] };

Mais mon compilateur GCC se plaint que l'élément initialisant n'est
pas constant :
"initializer element is not constant"


Eh oui. En C, un initialisateur pour un objet global doit être soit une
constante (une vraie, numérique et évaluable à la compilation, genre
22.0/7), soit l'adresse d'un objet (ce qui inclut les chaînes de
caractères).
Et fruits[1] n'est ni l'un ni l'autre, puisque c'est la valeur contenue dans
le deuxième objet du tableau fruits.

Si ce que tu veux, c'est initialiser les menus d'une certaine façon, avec la
possibilité de modifier, il faut écrire

const char * * menus[] = { &fruits[0], &fruits[1], &fruits[0],
&fruits[2], &fruits[0] };

(et il y a un niveau d'indirection supplémentaire); &fruits[1] est l'adresse
de l'objet, donc est valable pour initialiser.

Si par contre tu veux réellement donner des noms symboliques significatifs,
il faut en fait écrire

#define FRUITS0 "abricot"
#define FRUITS1 "banane"
#define FRUITS2 "cerise"

const char * menus[] { FRUITS0, FRUITS1, FRUITS0, FRUITS2, FRUITS0 };

ou peut-être

#define fruits(n) FRUITS##n
const char * menus[5] { fruits(0), fruits(1), fruits(0), fruits(2), fruits(0) };


Antoine

Lea GRIS
Le #1001371

Si ce que tu veux, c'est initialiser les menus d'une certaine façon, avec la
possibilité de modifier, il faut écrire

const char * * menus[] = { &fruits[0], &fruits[1], &fruits[0],
&fruits[2], &fruits[0] };


J'avais commencé avec une construction similaire mais le double
adressage complique inutilement le traitement. C'est en voulant
supprimer ce double adressage que j'ai justement rencontré cette
contrainte pour initialiser le tableau constant.


(et il y a un niveau d'indirection supplémentaire); &fruits[1] est l'adresse
de l'objet, donc est valable pour initialiser.

Si par contre tu veux réellement donner des noms symboliques significatifs,
il faut en fait écrire


Ce que je veux faire c'est peupler menu avec les pointeurs vers
différentes chaînes. Ces valeurs ne changeront jamais, donc rangées dans
une section data read-only à l'édition de liens.
J'arrive au résultat attendu avec :

const char * menu[] = [ "abricot", "cerise", "banane", "banane",
"abricot" ];

mais cela m'oblige à dupliquer des chaînes complètes qui seront unifiées
par le compilateur ensuite.

Bien entendu, avec des chaînes courtes ce n'est pas problématique. Pour
des chaînes longues, je pourrais aussi utiliser des #defines pour éviter
de retaper des chaînes longues pour plus de lisibilité du code et
limiter les risques d'erreurs.

Maintenant, dans le cadre d'expérimentations que je mène avec un code
différent, je pourrais souhaiter obtenir un tableau contenant des
pointeurs vers des fonctions ou toute autre forme de pointeurs vers des
données. Le tout étant pré-remplit à la compilation et ou à l'édition
des liens puisque ces valeurs ne changeront pas à l'exécution.



#define FRUITS0 "abricot"
#define FRUITS1 "banane"
#define FRUITS2 "cerise"

const char * menus[] > { FRUITS0, FRUITS1, FRUITS0, FRUITS2, FRUITS0 };

ou peut-être

#define fruits(n) FRUITS##n
const char * menus[5] > { fruits(0), fruits(1), fruits(0), fruits(2), fruits(0) };


oui

--
Léa Gris

Antoine Leca
Le #1005327
En news:47989d41$0$839$, Lea GRIS va escriure:
Ce que je veux faire c'est peupler menu avec les pointeurs vers
différentes chaînes. Ces valeurs ne changeront jamais, donc rangées
dans une section data read-only à l'édition de liens.
J'arrive au résultat attendu avec :

const char * menu[] = [ "abricot", "cerise", "banane", "banane",
"abricot" ];

mais cela m'oblige à dupliquer des chaînes complètes qui seront
unifiées par le compilateur ensuite.


Donc en fait, tu as une solution qui fonctionne parfaitement avec ton
compilateur (mais peut-être pas avec un autre), mais tu voudrais «
optimiser » le résultat, en forçant la position des chaînes dans un ordre
donné, histoire de forcer un éventuel compilateur bête à donner quand même
une solution intelligente...

Il y a sûrement une manière pour y arriver (cf. infra), mais d'abord je
voudrais souligner un point:
Au lieu de ton exemple, prenons une version « arrangée »

#define FRUITS0 "berry"
#define FRUITS1 "blackberry"
#define FRUITS2 "wild blackberry"

Même menus[], on laisse faire un compilateur qui cherche à compacter au
mieux, on regarde le résultat, surprise il n'y a plus QUE les "wild
blackberry" ! :+)

En fait, de part la nature des chaînes en C, certains compilateurs
compactent ce genre de chaînes, et les trois pointeurs vers "berry" sont
manipulés pour pointer en fait FRUITS2+10 !
Encore un exemple où les optimisations des compilateurs peuvent être plus
subtiles que celles des humains.


Sinon, pour obtenir ce que tu veux, tu peux essayer

const char
Fruits0[] = "abricot"
, Fruits1[] = "banane"
, Fruits2[] = "cerise"
;

#define fruits(n) Fruits##n
const char * menus[5] { fruits(0), fruits(1), fruits(0), fruits(2), fruits(0) };


Pas forcément très joli, mais cela donne le résultat escompté. Le principe
est transposable à un tableau de fonctions (en appelant Fruits0, Fruits1
etc. les fonctions).

Le problème avec les tableaux de chaînes comme on a ici, c'est que la
solution « normale »,

const char fruits[][10] { "abricot", "banane", "cerise" };
const char * menus[] { fruits[0], fruits[1], fruits[0], fruits[2], fruits[0] };

oblige à spécifier la longueur maximum des chaînes (ici 9 lettres), et de
plus réserve toujours cet espace, même si la chaîne est plus petite, ce qui
est gâché comme dirait Guy.



Antoine

Publicité
Poster une réponse
Anonyme