OVH Cloud OVH Cloud

modifier un const

16 réponses
Avatar
Jarod
Bonjour,

Je me suis rendu compte il y a peu qu'on pouvait modifier une variable
censée être constante par l'intermédiaire d'un pointeur, voici le code :

const int a = 0;
int *r;

cout << "r = " << r << endl;
cout << "&a = " << &a << endl;
cout << "a = " << a << endl << endl;

r = (int *) & a;
*r = 4;

cout << "r = " << r << endl;
cout << "*r = " << *r << endl;
cout << "&a = " << &a << endl;
cout << "a = " << a << endl << endl;

Bon, les affichages ne sont pas spécialement utiles mais ils montrent
quelquechose d'assez surprenant : on a 2 valeurs différentes pour une même
adresse !

Alors, si quelqu'un connaît le pourquoi du comment ça se fait, je suis tout
à son écoute parce que ça me turlupine ce problème.

Merci d'avance

6 réponses

1 2
Avatar
Gabriel Dos Reis
"Pierre Maurette" writes:


[...]

| [...]
| > Dans le cas d'un int const, ça m'étonnerait que tu trouves un
| > compilateur qui ne fait pas cette optimisation. Il faut de toute
| > façon que le compilateur puisse utiliser la valeur même (et non
| > la variable) dans des declarations du genre int t[a].
| J'en profite pour une question. Sur un compilateur Borland 5.6:
| const size_t a = 32;
| ...
| int t[a];
|
| "test.c" : E2313 Constant expression required in function main en
| ligne 10
|
| (que a soit déclaré localement ou globalement).
| Ce qui m'oblige à des #define

Oui mais lorsque le comité C a piqué la proposition initiale de BS, il a
délibérément décidé de ne pas tenir compte de l'implémentation de
l'expérience acquise. I.e. il a volontairement décidé de ne pas avoir
la même sémantique que C with Classes (et plus tard C++).

-- Gaby
Avatar
kanze
Pierre Maurette wrote:
[...]
C'est en effet l'intention des auteurs de la norme C, au
départ. Selon le compilateur et le système, en revanche...
Sur beaucoup de systèmes, le volatile marche exactement
comme on s'y attendrait, tant qu'il n'y a qu'un seul
processeur (ou autre composant hardware) qui s'y accède. La
plupart des compilateurs que j'ai vu, en revanche,
n'insertent pas de barrière de mémoire, ce qui est
nécessaire dès qu'il y a plus d'une entité hardware qui y
accèdent.


Qu'est-ce qu'une barrière de mémoire ? Un truc qui locke
certains accès pour la durée d'une opération (comme la prise
d'un jeton) ?


Non. C'est une instruction machine qui fonctionne sur les
requêtes mémoire à peu près comme un fflush() fonctionne sur un
flux (sauf qu'à l'encontre de fflush(), elle est bien défini et
en lecture et en écriture). Grosso modo, sur un processeur
moderne, quand tu émets une instruction de lecture ou d'écriture
mémoire, elle ne va pas forcément jusqu'à la mémoire.
Physiquement, par exemple, quand tu lis un octet, toute une
ligne de cache se présente sur le bus, et plus tard, quand tu
lis un autre octet, si le CPU voit qu'il a déjà les données
(parce qu'elles se trouvent dans une ligne de cache qu'il a lu
auparavent), il le prend là. De même, quand tu écris, le CPU ne
fait que « schéduler » une écriture, qui n'aura lieu que quelque
part dans le future. Et surtout, dans les deux opérations,
l'ordre n'est pas préservé : si tu lis a, puis b, les accès
mémoire peuvent bien avoir lieu dans l'ordre inverse, de même
que si tu les écris.

Seulement, parfois ça pose des problèmes. J'initialise un objet
en y écrivant, puis j'en écris l'adresse dans une variable
globale, de façon à ce que d'autres threads (qui tournent sur
d'autres processeurs) puissent y accéder. Seulement, l'écriture
du pointeur a lieu avant une des écritures de l'initialisation,
et l'autre thread accède à un objet non initialisé. Ou dans
l'autre thread, la lecture de l'objet a lieu avant la lecture du
pointeur (parce que par hazard, l'objet se trouvait sur une
ligne de cache que je lisais pour autre chose) -- du coup, je
trouve que l'objet est valide (pointeur non null), mais les
valeurs de l'objet dont je me sers sont celles d'avant
l'initialisation.

Alors, on y introduit les barrières de mémoire. Ce qui est
offert dépend de la machine. Pour IA-32, je crois que Intel
garantit que ça marche si tous les accès sont protégés par des
préfixes de lock (mais je ne suis pas sûr). Sur un Sparc, il y a
une instruction machine membar, avec des flags pour indiquer des
types de protection voulus. Pour cité le manuel Sparc (dans la
définition de l'instruction membar) :

MEMBAR introduces an order constraint between classes of
memory references appearing before the MEMBAR and memory
references following it in a program. [...]

A load has been performed when the value loaded has been
transmitted from memory and cannot be modified by another
processor. A store has been performed when the value stored
has become visible, that is, when the previous value can no
longer be read by any processor.

[... concernant les options dans l'instruction]

mmask<3> #StoreStore The effects of all stores appearing
prior to the MEMBAR instruction must
be visible to all processors before
the effect of any stores following
the MEMBAR. [...]
mmask<2> #LoadStore All loads appearing prior to the
MEMBAR instruction must have been
performed before the effect of any
stores following the MEMBAR is
visible to any other processor.
[... et ainsi de suite pour #StoreLoad et #LoadLoad]

Donc, dans le cas précédant, après l'initialisation, mais avant
d'écrire le pointeur, j'exécute un MEMBAR #StoreStore. Du côté
client, il faut un MEMBAR #LoadLoad entre la lecture du
pointeur et l'utilisation de l'objet.

Note que si j'utilise les primitives du système, je n'ai
normalement pas à m'en occuper : quelque part dans
l'implémentation de pthread_mutex_lock et de
pthread_mutex_unlock, il y a bien les barrières dont j'ai
besoin.

Note aussi qu'aucun compilateur C ou C++, à ma connaissance,
génèrent des instructions MEMBAR (ou leurs équivalents sur
d'autres machines -- mais je ne connais que la situation sur
Sparc sous Solaris en détail) -- même lors des accès
« volatile ».

[...]
Remarquez que la norme autorise à prendre l'adresses d'une
constante (au contraire d'un register par exemple).


Comment ça ? Tu peux prendre l'adresse de n'importe quel
objet. Même un objet déclaré régistre. Le mot clé régistre
(comme inline) n'est qu'une suggestion, et comme dit le note
dans la norme : « the hint can be ignored and in most
implementations it will be ignored if the address of the
object is take. »


Ben, ISO/IEC 9899:1999 (E), $6.7.1, note 100)

The implementation may treat any register declaration simply
as an auto declaration. However, whether or not addressable
storage is actually used, the address of any part of an object
declared with storage-class specifier register cannot be
computed, either explicitly (by use of the unary & operator as
discussed in 6.5.3.2) or implicitly (by converting an array
name to a pointer as discussed in 6.3.2.1). Thus, the only
operator that can be applied to an array declared with
storage-class specifier register is sizeof.


Je ne sais pas si tu te rends compte, mais tu as posté dans
fr.comp.lang.C++. La norme C que tu cites n'a pas de précédance
sur la norme C++. Et la norme C++ est claire :

A register specifier has the same seamntics as an autor
specifier, together with a hint to the implementation that
the object so declared will be heavily used.

C'est tout. Aucune restriction supplémentaire.

Dans la pratique, aujourd'hui, n'importe quel compilateur digne
de ce nom peut faire mieux que le programmeur. Surtout quand il
a assez de régistres pour jouer. C'est donc courant de
l'ignorer. (En général, en ce qui concerne le « will be heavily
used », le compilateur se fie plus aux sorties du profiler
qu'aux dires du programmeur.)

(Mais que je crois qu'en « most implementations »
aujourd'hui, la suggestion est systèmatiquement ignorée.
Sauf peut-être sur une architecture Intel, extrèmement
pauvre en règistres. Et même là, un bon compilateur saurait
mieux faire que le programmeur.)


C'est certain. J'ai déjà plusieurs fois donné *un* exemple
dans lequel certains compilateurs (les moins optimisants, en
fait) tiraient avantage d'un register.


En 1985, quand je compilais pour un 8086, register m'était bien
utile -- il m'était même arrivé une fois de scinder une fonction
en deux blocs, chacun avec deux variables déclarées register,
précisemment pour que ce ne soit pas les deux mêmes variables
dans les régistres dans les deux parties de la fonction.

Ça, c'était 1985. Aujourd'hui, si je me trouve devant un
problème semblable de performance, je donne les informations
sortie du profiler à l'optimisateur du compilateur, et lui, il
se débrouille. Ça marche très bien avec Sun CC. Je ne l'ai pas
essayé avec d'autres, mais je sais que g++, aCC (de HP), et le
compilateur C++ de SGI le supporte aussi, et il me semble avoir
entendu dire que c'était même le cas pour VC++.

Il s'agit d'un cas dans lequel le programmeur connaît le
contenu très particulier d'un flux de données (un fichier).
Mais le code était fait dans le but de montrer.

A partir de là, vous êtes dans l'indéfini.


Pas du tout. L'indéfini ne s'y introduit que quand on essaie
à modifier l'objet, pas avant.


Oui. Il est effectivement utile de préciser que l'opération
d'affectation castée n'est pas en elle même indéfinie. Idem
quand je parle du cast "sauvage" comme d'un point clé. D'un
autre coté, prenons le cas de Maurice qui ratatine Josiane. Le
point-clé est-il quand il achète à Pépé le Biterrois le 357
magnum ou quand il appuie sur la gachette du soufflant pour
défourailler la belle ?


C'est en effet une bonne analogie. Sur tous les plans : même si
c'est légal, on se méfie quand quelqu'un achête un 357 magnum.
De même qu'il faut devenir méfiant quand on va trop de casts de
ce genre. (Mais peut-être c'est innocent. Que le programmeur
avait à faire avec une interface historique qui ne connaissait
pas le const.)

[...]
Dans le cas d'un int const, ça m'étonnerait que tu trouves
un compilateur qui ne fait pas cette optimisation. Il faut
de toute façon que le compilateur puisse utiliser la valeur
même (et non la variable) dans des declarations du genre int
t[a].


J'en profite pour une question. Sur un compilateur Borland 5.6:
const size_t a = 32;
...
int t[a];

"test.c" : E2313 Constant expression required in function main
en ligne 10

(que a soit déclaré localement ou globalement).
Ce qui m'oblige à des #define


Est-ce que tu es certain d'avoir compiler en mode C++, et non en
mode C ? Parce que c'est bien illégal en C, mais ça a marché en
C++ depuis le départ. (Dans mon cas, le compilateur Zilog, circa
1989. Mais d'après mes souvenirs, c'était décrit marcher dans
TC++PL, première édition. Qui décrivait, entre autres, le mot
clé « overload », mais pas l'héritage multiple, sans parler des
templates ou des exceptions.)

Au fond, d'après ça, et tes commentaires sur le register, je me
démande si tu es en train de faire du C ou du C++. Parce qu'il
s'agit de deux langages différents, et il faut bien décider de
faire un ou l'autre, et ne pas faire les deux à la fois (dans la
même unité de compilation). Et si c'est le C qui t'intéresse,
plus que le C++, tu trouveras d'avantage d'experts dans la
matière dans le groupe C.

--
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
kanze
Pierre THIERRY wrote:
r = (int *) & a;


Au passage, en C++, par lisibilité, on lui préférera :

r = const_cast<int*>(&a);


Non seulement par lisibilité. Si on s'est trompé, et a était en
fait un char const*, le compilateur râlera (puisque j'ai dit que
je ne voulais changer que le const).

Je me rappelle bien le « bon » vieux temps, quand on perdait des
journées entières à chercher des erreurs dues au fait que ce que
le programmeur croyait un static_cast était en fait un
reinterpret_cast. (Dans les faits : un A* en B*, où A et B
étaintt des classes dans une hièrarchie d'héritage, mais des
classes soeurs, et non ascendantes ou descendantes. Et que si le
programmeur avait écrit static_cast, le compilateur l'aurait
arrêté tout de suite.)

--
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
Pierre Maurette
[...]
Non. C'est une instruction machine qui fonctionne sur les
requêtes mémoire à peu près comme un fflush() fonctionne sur un
flux (sauf qu'à l'encontre de fflush(), elle est bien défini et
en lecture et en écriture).
[...]

J'aurais dû m'en douter, avec un meilleur anglais: SFENCE, MFENCE,
LFENCE, etc...


J'en profite pour une question. Sur un compilateur Borland 5.6:
const size_t a = 32;
...
int t[a];

"test.c" : E2313 Constant expression required in function main
en ligne 10

(que a soit déclaré localement ou globalement).
Ce qui m'oblige à des #define


Est-ce que tu es certain d'avoir compiler en mode C++, et non en
mode C ? Parce que c'est bien illégal en C, mais ça a marché en
C++ depuis le départ. (Dans mon cas, le compilateur Zilog, circa
1989. Mais d'après mes souvenirs, c'était décrit marcher dans
TC++PL, première édition. Qui décrivait, entre autres, le mot
clé « overload », mais pas l'héritage multiple, sans parler des
templates ou des exceptions.)
Je confirme que c'est bien en C++ que Borland 5.6 m'envoie paître.

Ayant parfois fait l'erreur inverse avec gcc à cause d'un IDE qui
ajoutait un -c c++, je vérifie souvent, et dans le sens C pour du C++,
l'erreur est quand même vite vue.

Au fond, d'après ça, et tes commentaires sur le register, je me
démande si tu es en train de faire du C ou du C++. Parce qu'il
s'agit de deux langages différents, et il faut bien décider de
faire un ou l'autre, et ne pas faire les deux à la fois (dans la
même unité de compilation). Et si c'est le C qui t'intéresse,
plus que le C++, tu trouveras d'avantage d'experts dans la
matière dans le groupe C.
J'admets que le C++ ne me passionne pas. De là à confondre... (OK, je

me suis trompé de norme pour la citation sur la prise d'adresse d'un
register).
Je ne souhaite pas vraiment développer des classe, et surtout pas des
hiérarchies de classes. En revanche je les utilise. Je ne pense pas
être unique à vouloir utiliser C++Builder par exemple, et à être
incapable d'écrire une bibliothèque de ce calibre. Donc, je me contente
d'utiliser le plus proprement possible en les complètant les classes
dérivées mises à ma disposition, voire en créer de très simples. Pour
d'autres raisons, je m'intéresse au C pur et dur, mais je dois dire que
je me fiche un peu de C99 au profit d'un tronc commun C89/C++98. Un
exemple: je caste les pointeurs void.

--
Pierre


Avatar
kanze
Pierre Maurette wrote:
[...]
Non. C'est une instruction machine qui fonctionne sur les
requêtes mémoire à peu près comme un fflush() fonctionne sur
un flux (sauf qu'à l'encontre de fflush(), elle est bien
défini et en lecture et en écriture).
[...]

J'aurais dû m'en douter, avec un meilleur anglais: SFENCE,
MFENCE, LFENCE, etc...


C'est en effet un autre nom pour plus ou moins la même chose.
Il y a une différence formelle, je crois, entre un « memory
barrier » et une « fence », mais ils servent aux même fins. Si
j'utilise le mot barrière de mémoire, c'est bien parce que sur
un Sparc, l'instruction s'appelle MEMBAR, qui est un raccourci
de MEMory BARrier.

J'en profite pour une question. Sur un compilateur Borland 5.6:
const size_t a = 32;
...
int t[a];

"test.c" : E2313 Constant expression required in function main
en ligne 10

(que a soit déclaré localement ou globalement). Ce qui
m'oblige à des #define


Est-ce que tu es certain d'avoir compiler en mode C++, et
non en mode C ? Parce que c'est bien illégal en C, mais ça a
marché en C++ depuis le départ. (Dans mon cas, le
compilateur Zilog, circa 1989. Mais d'après mes souvenirs,
c'était décrit marcher dans TC++PL, première édition. Qui
décrivait, entre autres, le mot clé « overload », mais pas
l'héritage multiple, sans parler des templates ou des
exceptions.)


Je confirme que c'est bien en C++ que Borland 5.6 m'envoie paître.


Je me démandé aussi parce que dans le message d'erreur, il est
question d'un fichier source « test.c ». Or, tous les
compilateurs que j'utilise d'habitude vont traiter une source
dont le nom se termine en .c comme une source C, et non comme
une source C++. Par défaut, en tout cas ; il y a en général une
option (/Tp pour le compilateur Microsoft) qui le force à le
traiter comme C++.

Ayant parfois fait l'erreur inverse avec gcc à cause d'un IDE
qui ajoutait un -c c++, je vérifie souvent, et dans le sens C
pour du C++, l'erreur est quand même vite vue.

Au fond, d'après ça, et tes commentaires sur le register, je
me démande si tu es en train de faire du C ou du C++. Parce
qu'il s'agit de deux langages différents, et il faut bien
décider de faire un ou l'autre, et ne pas faire les deux à
la fois (dans la même unité de compilation). Et si c'est le
C qui t'intéresse, plus que le C++, tu trouveras d'avantage
d'experts dans la matière dans le groupe C.


J'admets que le C++ ne me passionne pas. De là à confondre...
(OK, je me suis trompé de norme pour la citation sur la prise
d'adresse d'un register).


C'était un ensemble de chose qui me faisait poser la question.
La citation de la norme C (et même sans la norme, le fait que tu
pensais qu'on ne pourait pas prendre l'adresse d'une variable
« register »), mais aussi l'erreur ci-dessus, et le fait que le
nom de fichier était en .c.

Je ne souhaite pas vraiment développer des classe, et surtout
pas des hiérarchies de classes. En revanche je les utilise. Je
ne pense pas être unique à vouloir utiliser C++Builder par
exemple, et à être incapable d'écrire une bibliothèque de ce
calibre.


J'imagine que la plupart des utilisateurs d'un compilateur,
n'importe lequel, serait incapable d'écrire une bibliothèque
standard, au moins une de la qualité à laquelle on s'attend.

Donc, je me contente d'utiliser le plus proprement possible en
les complètant les classes dérivées mises à ma disposition,
voire en créer de très simples. Pour d'autres raisons, je
m'intéresse au C pur et dur, mais je dois dire que je me fiche
un peu de C99 au profit d'un tronc commun C89/C++98. Un
exemple: je caste les pointeurs void.


Alors, tu es bien un C++'iste:-). (Mais pas au fond ; un vrai
C++'iste de souche n'utilise même pas de pointeurs void:-).)

--
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
Pierre Maurette
[...]
Je me démandé aussi parce que dans le message d'erreur, il est
question d'un fichier source « test.c ». Or, tous les
compilateurs que j'utilise d'habitude vont traiter une source
dont le nom se termine en .c comme une source C, et non comme
une source C++. Par défaut, en tout cas ; il y a en général une
option (/Tp pour le compilateur Microsoft) qui le force à le
traiter comme C++.
Honte à moi. Shame on me. :D

Je me suis minablement mélangé les crayons, et j'ai du mal à comprendre
ce que j'ai fait lors de ma deuxième affirmation. C'est pourtant
évident de placer quelque chose qui ne compile pas en C, plus facile
que le contraire, et il me semble bien que je l'avais fait. Je n'ai pas
du lire le message d'erreur, qui devait signaler autre chose, ou plutôt
penser y lire ce que je voulais y lire.
test.c n'était même pas sigificatif, j'avais édité le nom du fichier
avant de poster. En fait, j'utilise pour ce genre de test un IDE multi
compilateurs qui me piège assez souvent (C++BuilderX).

--
Pierre

1 2