С++ 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';

Все другие значения, отличные от указателей, являются результатами указателей разыменования.