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

10 réponses

1 2
Avatar
Serge Paccalin
Le dimanche 15 mai 2005 à 22:21:40, Jarod a écrit dans
fr.comp.lang.c++ :

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 :


Non, on ne peut pas. Quand on fait ça, on a un comportement indéfini du
programme.

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 !


Voilà, un exemple de comportement indéfini.

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.


Comme « a » est const, le compilateur s'est probablement permis de
remplacer toutes les occurrences de « a » par sa valeur 0. Vous pouvez
changer la valeur en mémoire tant que vous voulez, c'est trop tard. Le
code compilé ne va pas lire la valeur en mémoire, il la contient déjà :

cout << "a = " << a << endl << endl;

a été compilé comme :

cout << "a = " << 0 << endl << endl;

Vous ne pouvez pas changer « a » à l'exécution.

--
___________ 15/05/2005 22:27:53
_/ _ _`_`_`_) Serge PACCALIN -- sp ad mailclub.net
_L_) Il faut donc que les hommes commencent
-'(__) par n'être pas fanatiques pour mériter
_/___(_) la tolérance. -- Voltaire, 1763

Avatar
Jarod
Merci beaucoup,
Cette possibilité m'a l'air plus logique que la double valeur pour une
adresse ;-)
Avatar
Jarod
Merci beaucoup,
Cette possibilité m'a l'air plus logique que la double valeur pour une
adresse ;-)
Avatar
Jarod
Merci beaucoup,
Cette possibilité m'a l'air plus logique que la double valeur pour une
adresse ;-)
Avatar
Pierre Maurette
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 !
Pour compléter vos tests (j'en fais également beaucoup !), vous pouvez

ajouter à la fin:
cout << "*(&a) = " << *((int*)(&a)) << endl;
et au début, essayer:
const volatile int a = 45;
(j'ai mis 45 à la place de votre 0, qui est une valeur "un peu
particulière", mais c'est certainement sans importance)
Résultat du volatile dépend chez moi du compilateur utilisé, dans un
cas (gcc) il suggère clairement au compilateur d'accéder à la mémoire à
chaque accès à a. C'est à mon avis un bon comportement, le volatile
const suggérant une variable en lecture seule et modifiable "à
l'extérieur du programme". Ça pourrait être le registre d'entrée d'un
coupleur E/S.


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.
Le point clé, c'est le cast sauvage const int* -> int*. Remarquez que

la norme autorise à prendre l'adresses d'une constante (au contraire
d'un register par exemple).
A partir de là, vous êtes dans l'indéfini. Ici, chez moi et chez vous,
le compilateur "optimise" en remplaçant a dans le code certainement par
sa valeur immédiate. Et même, si a == 0, le compilateur, pour
l'affectation x = a, peut générer un truc du genre CLEAR x si ça
existe. Il peut remplacer x = a*(b + c - d/7) par x = 0.
Dernier point: il est fort possible que les données constantes soient
placées dans une zone mémoire à lecture seule, et que la tentative
d'écriture se solde par une violation d'accès.

--
Pierre

Avatar
kanze
Pierre Maurette wrote:

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 !


Pour compléter vos tests (j'en fais également beaucoup !),
vous pouvez ajouter à la fin:

cout << "*(&a) = " << *((int*)(&a)) << endl;
et au début, essayer:
const volatile int a = 45;
(j'ai mis 45 à la place de votre 0, qui est une valeur "un peu
particulière", mais c'est certainement sans importance)

Résultat du volatile dépend chez moi du compilateur utilisé,
dans un cas (gcc) il suggère clairement au compilateur
d'accéder à la mémoire à chaque accès à a. C'est à mon avis un
bon comportement, le volatile const suggérant une variable en
lecture seule et modifiable "à l'extérieur du programme". Ça
pourrait être le registre d'entrée d'un coupleur E/S.


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.

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.


Le point clé, c'est le cast sauvage const int* -> int*.


Pas vraiment. Le cast est tout à fait légal. Le point clé, c'est
qu'il essaie de modifier un objet qui est const. Ce qui provoque
un comportement indéfini.

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. » (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.)

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.

Ici, chez moi et chez vous, le compilateur "optimise" en
remplaçant a dans le code certainement par sa valeur
immédiate.


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].

Et même, si a == 0, le compilateur, pour l'affectation x = a,
peut générer un truc du genre CLEAR x si ça existe. Il peut
remplacer x = a*(b + c - d/7) par x = 0. Dernier point: il est
fort possible que les données constantes soient placées dans
une zone mémoire à lecture seule, et que la tentative
d'écriture se solde par une violation d'accès.


C'était aussi une des intentions des auteurs de la norme C. (Je
ne sais pas si c'était l'intention de Stroustrup, quand il a
introduit le const dans C++, mais c'était bien une des
intentions des auteurs de la norme C, -- probablement la plus
importante chez certains -- quand ils ont emprunté const de
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
Pierre THIERRY
Le Sun, 15 May 2005 22:21:40 +0200, Jarod a écrit :
r = (int *) & a;


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

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

Nouvellement,
Nowhere man
--

OpenPGP 0xD9D50D8A

Avatar
Horst Kraemer
"Jarod" wrote:

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.


La réponse est simple. Une tentative de changer un objet déclaré const
peut produire des effets imprévisibles.

Quand tu définis

const int i=0;

tu promets au compilateur de ne pas modifier le contenu de i. Donc il
se rappellera au cours de la compilation que i a la valeur 0 et met un
0 si tu dis "i" sans regarder si la variable i contient toujours la
valeur 0.

Si tu ne tiens pas ta promesse - tant pis pour toi ;-)

--
Horst

--
Lâche pas la patate!

Avatar
Pierre Maurette
[...]
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) ?

[...]
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.


(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. 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 ?


[...]
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

--
Pierre


Avatar
Gabriel Dos Reis
writes:

[...]

| > Et même, si a == 0, le compilateur, pour l'affectation x = a,
| > peut générer un truc du genre CLEAR x si ça existe. Il peut
| > remplacer x = a*(b + c - d/7) par x = 0. Dernier point: il est
| > fort possible que les données constantes soient placées dans
| > une zone mémoire à lecture seule, et que la tentative
| > d'écriture se solde par une violation d'accès.
|
| C'était aussi une des intentions des auteurs de la norme C. (Je
| ne sais pas si c'était l'intention de Stroustrup, quand il a
| introduit le const dans C++, mais c'était bien une des
| intentions des auteurs de la norme C, -- probablement la plus
| importante chez certains -- quand ils ont emprunté const de
| C++.)

L'histoire de « const » est expliquée en détail dans D&E et par le
passé plusieurs extraits concernant ce point précis. Mais la rumeur
urbaine continue de se répandre...

-- Gaby
1 2