Странное поведение при использовании указателей

Когда я запускаю этот код в MS VS С++ 2010:

#include <iostream>

int main() {
    const int a = 10;
    const int *b = &a;
    int *c = (int *)b;
    *c = 10000;
    std::cout << c << " " << &a << std::endl;
    std::cout << *c << " " << a << " " << *(&a) << std::endl;
    return 0;
}

Вывод:

0037F784 0037F784
10000 10 10

Мотивацией для написания этого кода было это предложение от "языка программирования С++" от Stroustrup: "Можно явно удалить ограничения на указатель на const путем явного преобразования типов".

Я знаю, что попытка изменить константу концептуально неверна, но я нахожу этот результат довольно странным. Может ли кто-нибудь объяснить причину этого?

Ответ 1

Давайте начнем с очевидного: некоторые из них зависят от платформы и компилятора.

Для начала см. эту статью в Явное преобразование типов и, в частности:

Указатель на объект типа const может быть помещен в указатель на не const. Результирующий указатель будет ссылаться на оригинал object. Объект типа const или ссылка на объект объекта Тип const может быть переведен в ссылку на тип const. результирующая ссылка будет ссылаться на исходный объект. Результат попытка изменить этот объект с помощью такого указателя или ссылки будет либо вызывать исключение адресации, либо быть таким же, как если бы исходный указатель или ссылка ссылались на неконстантный объект. это реализация зависит от того, существует ли исключение адресации.

Итак, это объясняет, почему он может позволить вам изменять переменную без сукинга.

Обратите внимание, что вы можете добиться того же самого с помощью операторов трансляции, как и то, что сделает компилятор для вас, как описано в этой статье о операторах трансляции с порядком приоритета.

Однако реальный трюк здесь находится в модели памяти. Статически назначенная переменная, такая как const int a, на самом деле никогда не может иметь никакого "физического" местоположения в памяти и просто заменяется на месте в время компиляции. (Я пытаюсь перенести палец на фактическую ссылку для этого, но до сих пор самое близкое и лучшее, что я мог бы захватить, было это (очень приятно) SO answer to - это память, выделенная для статической переменной, которая никогда не используется? - Если кто-либо найдет фактическую ссылку, сообщите нам об этом.)

Итак, вот компилятор просто вас обманывает и пытается как можно больше понять вашу арифметику указателя, но в итоге заменяет фактические значения для 2 последних частей вашего второго вызова cout a.

Ответ 2

Причина в том, что это поведение undefined.

Цитата Stroustrup, вероятно, относится к случаю, когда объект не был объявлен const, но у вас есть только указатель const.

то есть. Это хорошо определено (с использованием c-style cast, как они появляются):

int a{10};
const int* pa = &a;

int* b = (int*)pa;
*b = 5;

И это undefined:

const int a{10};
const int* pa = &a;

int* b = (int*)pa;
*b = 5;

Попытка изменить объявленный объект const, однако вы получите указатель не const const, это UB.