Хотелось бы понять, как С++ обрабатывает константы базовых типов.
Что будет, если при помощи плясок с бубном и указателями изменить значение, находящееся в ячейке памяти, где, по идее, и должна содержаться константа?
Есть следующий код:
#include
using namespace std;
int main(int argc, char** argv) {
const int number = 45;
const int * constPoint = &number;
cout << "constPoint " << constPoint << endl;
int * point = (int *) constPoint;
cout << " point " << point << endl;
* point = 54;
cout << "&number " << &number << "; number " << number << endl;
cout << " point " << point << "; *point " << *point << endl;
}
Вывод у меня получается следующий:
constPoint 0x7fff1b73a6ec
point 0x7fff1b73a6ec
&number 0x7fff1b73a6ec; number 45
point 0x7fff1b73a6ec; *point 54
Таким образом, значение константы не изменилось, хотя число, которое содержится в ячейке по адресу этой константы, уже другое.
Как объяснить подобное поведение?
Я не собираюсь такой код никогда использовать на практике, просто хочу понять, где хранятся константы.
Ответы
Ответ 1
Для начала: по стандарту, изменение при помощи трюков с указателями констант есть undefined behaviour. Может случится всё, что угодно в любой точке программы.
Компилятор имеет право не хранить константу вообще нигде, если вы к ней не обращаетесь
Или имеет право хранить, если ему покажется так лучше. Или хранить в памяти только для чтения. Или хранить по общему адресу с какой-нибудь инструкцией кода, которая численно равна этой же константе.
Например, если компилятор видит обращение к константе, он может встроить её в точк
вызова (он ведь знает значение на этапе компиляции), может обратиться к этой констант
по адресу в любой момент, может даже выразить другие константы через эту (например, если у вас есть константа 1024, то обращение к 2048 он имеет право представить как 1024 << 1 из соображений эффективности).
Хуже того, если где-то компилятор выяснил для себя, что константа чётная, и её младши
бит равен 0, и на основании этой информации смог исключить какие-нибудь проверки из кода, то теперь проверки не пройдут, и код может крешнуться в любой момент.
На UB обычно основаны дыры в безопасности программ. Например, если компилятор обоснованн
считает, что длина строки 45, и под неё достаточно выделить буфер такой длины, в то время как вы, обманув компилятор, подсунете ему строку длиной 54, получится классический срыв стека.
Резюме: У компилятора нет никаких «обязательств» или «принципов» по работе с константами
Он имеет право делать что угодно. Наоборот, это программист, если уж обещал, что какое-то значение есть константа, не имеет права его менять.
Ответ 2
Согласно стандарту C++ (7.1.6.1 The cv-qualifiers)
4 Except that any class member declared mutable (7.1.1) can be
modified, any attempt to modify a const object during its lifetime
(3.8) results in undefined behavior
Есть один анахронизм в C, когда даже попытка изменить неконстантный объект приводит к неопределенному поведению программы. Речь идет о строковых литералах в языке C.
В C++ строковые литералы имеют типы константных символьных массивов. Например, строковы
литерал "Hello" имеет тип const char[6]. Однако в C строковые литералы имеют типы неконстантны
символьных массивов. Поэтому этот же самый строковый литерал "Hello" в C имеет тип char[6]. Тем не менее вы не можете изменить строковый литерал в C, также как вы не можете изменить строковый литерал в C++.
Из стандарта C (6.4.5 String literals)
7 It is unspecified whether these arrays are distinct provided their
elements have the appropriate values. If the program attempts to
modify such an array, the behavior is undefined.
То, что в C строковые литералы имеют типы неконстантных символьных массивов, очевидн
связано с поддержкой уже существующей кодовой базы на момент введения квалификатора const в стандарт языка C.
Ответ 3
Компилятор умный и знает, что константу можно заинлайнить. Но есть возможность убедить его, что эта константа не слишком константная:
volatile const int number = 45;
Полный код: http://codepad.org/tNlZoNkE
Вариант с кастом в void* для вывода: http://codepad.org/EoqOgd6t
PS: Насколько я помню, volatile const это корректная ситуация, которая не должн
приводить к undefined behavior.
он имеет полное право предполагать, что свой код точно не меняет const-объекты.
Для volatile это неважно. Представим, что есть нечто внешнее, что "совершенно случайно
меняет ту же константу на те же значения, что мы написали. А в самой программе вместо изменяющего кода стоят nop'ы. Такой вариант абсолютно точно корректен и не вызывает UB.
Значит, если UB есть, то его вызывает не изменившееся значение, а сама операция записи
Есть ли какой-то случай, в котором это возможно? Приходит в голову только read-onl
страница в памяти, но может ли компилятор так поступить? Локальную переменную он поместит в стек, который однозначно не readonly. Остаются глобальные. (Поля объектов попадают в одну из этих категорий.) Или есть ещё ситуации, когда операция записи может что-то испортить?
Комментариев нет:
Отправить комментарий