С++ const изменился с помощью указателя, или это так?
В c можно изменить const с помощью указателей, например:
//mainc.c
#include <stdio.h>
int main(int argc, char** argv) {
const int i = 5;
const int *cpi = &i;
printf(" 5:\n");
printf("%d\n", &i);
printf("%d\n", i);
printf("%d\n", cpi);
printf("%d\n", *cpi);
*((int*)cpi) = 8;
printf(" 8?:\n");
printf("%d\n", &i);
printf("%d\n", i);
printf("%d\n", cpi);
printf("%d\n", *cpi);
}
Константа изменяется, как видно на выходе:
![mainc output]()
Если мы попробуем то же самое в С++:
//main.cpp
#include <iostream>
using std::cout;
using std::endl;
int main(int argc, char** argv) {
const int i = 5;
const int *cpi = &i;
cout << " 5:" << '\n';
cout << &i << '\n';
cout << i << '\n';
cout << cpi << '\n';
cout << *cpi << '\n';
*((int*)cpi) = 8;
cout << " 8?:" << '\n';
cout << &i << '\n';
cout << i << '\n';
cout << cpi << '\n';
cout << *cpi << '\n';
int* addr = (int*)0x28ff24;
cout << *addr << '\n';
}
Результат не так ясен:
![main output]()
От выхода выглядит, что i
по-прежнему 5
и все еще находится в 0x28ff24
, поэтому константа не изменяется. Но в то же время cpi
также 0x28ff24
(то же, что и &i
), но значение, на которое он указывает, равно 8
(не 5
).
Может кто-нибудь объяснить, какая здесь магия?
Разъяснение здесь: qaru.site/info/335320/...
Ответы
Ответ 1
Поведение при отбрасывании const
из переменной (даже с помощью указателя или ссылки в С++), которая изначально была объявлена как const
, а затем впоследствии пыталась изменить переменную с помощью этого указателя или ссылки, равна undefined.
Таким образом, изменение i
, если оно объявлено как const int i = 5;
- это поведение undefined: результат, который вы наблюдаете, является проявлением этого.
Ответ 2
Это поведение undefined в соответствии с C11 6.7.3/6:
Если сделана попытка изменить объект, определенный с помощью const-квалифицированный тип с использованием lvalue с неконстантно-квалифицированными тип, поведение undefined.
(С++ будет иметь аналогичный нормативный текст.)
И поскольку это поведение undefined, все может случиться. В том числе: странный вывод, сбои в работе программы, "кажется, работает нормально" (эта сборка).
Ответ 3
Правило преобразования const_cast<Type *>()
или с-типа (Type *)
:
Преобразование заключается в удалении объявления const
, а не для удаления const
самого значения (объекта).
const Type i = 1;
// p is a variable, i is an object
const Type * p = &i; // i is const --- const is the property of i, you can't remove it
(Type *)p; // remove the const of p, instead the const of i ---- Here p is non-const but i is ALWAYS const!
Теперь, если вы попытаетесь изменить значение i
на p
, оно Undefined Поведение, потому что i
ВСЕГДА const.
Когда использовать такой вид преобразования?
1) Если вы можете убедиться, что указанное значение НЕ const.
например,
int j = 1;
const int *p = &j;
*(int *)p = 2; // You can change the value of j because j is NOT const
2) Заостренное значение const, но вы ТОЛЬКО читаете его и НИКОГДА его не изменяете.
Если вам действительно нужно изменить значение const, пожалуйста, переконфигурируйте код, чтобы избежать такого рода случаев.
Ответ 4
Итак, после некоторого размышления, я думаю, я знаю, что здесь происходит. Хотя это зависит от архитектуры/реализации, поскольку это поведение undefined, как указал Мариан. Моя настройка - mingw 5.x 32bit на Windows 7 64 бит, если кто-то заинтересован.
С++ const действуют как #defines, g++ заменяет все ссылки i
своим значением в скомпилированном коде (поскольку я является константой), но он также записывает 5
(i value) в некоторый адрес в памяти, чтобы предоставить доступ к i
с помощью указателя (указатель манекена). И заменяет все вхождения &i
на этот адрес (не совсем это делает компилятор, но вы знаете, что я имею в виду).
В C consts обрабатываются в основном как обычные переменные. С той лишь разницей, что компилятор не позволяет напрямую их изменять.
Вот почему Бьярне Страуструп в своей книге говорит, что вам не нужны #defines в С++.
Здесь приведено доказательство:
![введите описание изображения здесь]()
Ответ 5
Это нарушение правила строгого сглаживания (компилятор предполагает, что два указателя разных типов никогда не ссылаются на одно и то же место памяти) в сочетании с оптимизацией компилятора (компилятор не выполняет второй доступ к памяти для чтения i
, но использует предыдущая переменная).
EDIT (как указано в комментариях):
Из рабочего проекта стандарта ISO С++ (N3376):
"Если программа пытается получить доступ к сохраненному значению объекта через glvalue другого, чем один из следующих типов, поведение undefined [...] - стандартная версия динамического типа объект, [...] - тип, который является подписанным или неподписанным типом соответствующая cv-квалификационной версии динамического типа объект, [...] - тип, который является (возможно, cv-квалифицированным) базовым классом тип динамического типа объекта"
Насколько я понимаю, это указывает на то, что, возможно, cv-квалифицированный тип может использоваться как псевдоним, но не тот, который не был бы квалифицированным для типа cv, может быть.
Ответ 6
Было бы более полезно узнать, что делает один конкретный компилятор с определенными флагами с этим кодом, чем то, что делает "C" или "С++", потому что ни C, ни С++ ничего не делают с таким кодом. Его поведение undefined. Все может случиться.
Было бы, например, вполне законным придерживаться переменных const
на странице памяти только для чтения, что приведет к сбою оборудования, если программа попытается записать на него. Или терпеть неудачу, если вы попытаетесь написать письмо. Или повернуть разыменованный int*
перевод из const int*
во временную копию, которая может быть изменена без ущерба для оригинала. Или изменить каждую ссылку на эту переменную после переназначения. Или для рефакторинга кода в предположении, что переменная can const
не изменяется, чтобы операции выполнялись в другом порядке, и вы в конечном итоге модифицируете переменную, прежде чем считаете, что это произошло или не изменилось после нее. Или сделать i
псевдоним для других ссылок на константу 1
и изменить их тоже в другом месте программы. Или сломать программный инвариант, который делает ошибку программы совершенно непредсказуемыми. Или распечатать сообщение об ошибке и прекратить компиляцию, если он поймает такую ошибку. Или для того, чтобы поведение зависело от фазы Луны. Или что-нибудь еще.
Существуют комбинации компиляторов и флагов и целей, которые будут делать эти вещи, за исключением, возможно, ошибки фазы-луны. Самый смешной вариант, который Ive слышал о том, что в некоторых версиях Fortran вы можете установить константу 1 равную -1, и все циклы будут работать назад.
Написание производственного кода, как это, ужасная идея, потому что ваш компилятор почти наверняка не гарантирует, что этот код будет делать в вашей следующей сборке.
Ответ 7
Короткий ответ заключается в том, что правила декларации С++ 'const' позволяют использовать константное значение непосредственно в тех местах, где C пришлось бы разыменовывать переменную. I.e, С++ компилирует оператор
cout << i << '\n';
как будто это то, что было написано на самом деле, было
cout << 5 << '\n';
Все другие значения, отличные от указателей, являются результатами указателей разыменования.