Почему я могу изменить локальную переменную const посредством приведения указателей, но не глобальной в C? - PullRequest
4 голосов
/ 03 июня 2009

Я хотел изменить значение константы с помощью указателей.

Рассмотрим следующий код

int main()
{
    const int const_val = 10;
    int *ptr_to_const = &const_val;

    printf("Value of constant is %d",const_val);
    *ptr_to_const = 20;
    printf("Value of constant is %d",const_val);
    return 0;
}

Как и ожидалось, значение константы изменено.

но когда я попробовал тот же код с глобальной константой, я получаю следующую ошибку во время выполнения. Репортер сбоев Windows открывается. Исполняемый файл останавливается после печати первой инструкции printf в этой инструкции "* ptr_to_const = 20;"

Рассмотрим следующий код

const int const_val = 10;
int main()
{
    int *ptr_to_const = &const_val;
    printf("Value of constant is %d",const_val);
    *ptr_to_const = 20;
    printf("Value of constant is %d",const_val);
    return 0;
}

Эта программа скомпилирована в среде mingw с IDE кодовых блоков.

Может кто-нибудь объяснить, что происходит?

Ответы [ 7 ]

16 голосов
/ 03 июня 2009

Это константа, и вы все равно используете некоторые приемы, чтобы изменить ее, так что неопределенное поведение приводит к. Глобальная константа, вероятно, находится в постоянной памяти и поэтому не может быть изменена. Когда вы пытаетесь это сделать, вы получаете ошибку во время выполнения.

В стеке создается постоянная локальная переменная, которую можно изменять. Таким образом, вам не нужно менять константу в этом случае, но это может привести к странным вещам. Например, компилятор мог использовать значение константы в разных местах вместо самой константы, так что «изменение константы» не дает никакого эффекта в этих местах.

7 голосов
/ 03 июня 2009

Это только для чтения!

По сути, ваш компьютер разрешает виртуальные и физические адреса с использованием двухуровневой системы таблиц страниц. Наряду с этой грандиозной структурой данных добавляется специальный бит, представляющий, читается ли страница. Это полезно, потому что пользовательские процессы, вероятно, не должны перезаписывать свою собственную сборку (хотя самоизменяющийся код - это круто). Конечно, они, вероятно, также не должны перезаписывать свои собственные постоянные переменные.

Вы не можете поместить переменную "const" на уровне функций в память только для чтения, потому что она находится в стеке, где она ДОЛЖНА быть на странице чтения-записи. Тем не менее, компилятор / компоновщик видит ваше const и делает вам одолжение, помещая его в постоянную память. Очевидно, перезапись, которая вызовет всевозможные несчастья для ядра, которое устранит этот гнев в процессе, прекратив его.

3 голосов
/ 03 июня 2009

Удаление константности указателя в C и C ++ безопасно только в том случае, если вы уверены, что указанная переменная изначально была неконстантной (и у вас просто есть константный указатель на нее). В противном случае он не определен, и, в зависимости от вашего компилятора, фазы луны и т. Д., Первый пример также может быть неудачным.

1 голос
/ 08 августа 2016

Здесь есть две ошибки. Первый из них:

int *ptr_to_const = &const_val;

, что является нарушением ограничения в соответствии с C11 6.5.4 / 3 (более ранние стандарты имели похожий текст):

1010 * Ограничения *

Преобразования, в которых используются указатели, отличные от тех, которые разрешены ограничениями из 6.5.16.1, должны быть определены посредством явного приведения

Преобразование из const int * в int * недопустимо из-за ограничений 6.5.16.1 (которые можно посмотреть здесь ).

Смущает, что когда некоторые компиляторы сталкиваются с нарушением ограничения, они пишут «предупреждение» (или даже вообще ничего, в зависимости от переключателей) и делают вид, что вы написали что-то еще в своем коде, и продолжаете. Это часто приводит к программам, которые ведут себя не так, как ожидал программист, или фактически не ведут себя каким-либо предсказуемым образом. Почему компиляторы делают это? Бьет меня, но это, безусловно, создает бесконечный поток подобных вопросов.


gcc, похоже, работает так, как если бы вы написали int *ptr_to_const = (int *)&const_val;.

Этот фрагмент кода не является нарушением ограничения, поскольку используется явное приведение. Однако это подводит нас ко второй проблеме. Затем строка *ptr_to_const = 20; пытается выполнить запись в объект const. Это вызывает неопределенное поведение , соответствующий текст из Стандарта находится в 6.7.3 / 6:

Если предпринята попытка изменить объект, определенный с помощью const-квалифицированного типа, с помощью lvalue с неконстантным типом, поведение не определено.

Это правило семантическое, а не ограничение, что означает, что стандарт не требует, чтобы компилятор выдавал какие-либо предупреждения или сообщения об ошибках. Программа просто неверна и может вести себя бессмысленно, с любыми странными симптомами, включая, помимо прочего, то, что вы наблюдали.

1 голос
/ 03 июня 2009

Вы даже не должны ожидать, что значение будет изменено с самого начала. Согласно стандарту, это неопределенное поведение. Это неправильно как с глобальной переменной, так и в первую очередь. Только не делай этого :) Это могло бы привести к сбою другим способом, или локальным, и глобальным.

0 голосов
/ 18 августа 2015

Примечание: это предназначено как ответ на Можем ли мы изменить значение объекта, определенного с помощью констант через указатели? , который ссылается на этот вопрос как дубликат.

Стандарт не предъявляет никаких требований к тому, что компилятор должен делать с кодом, который создает указатель на объект const и пытается выполнить запись в него. Некоторые реализации, особенно встроенные, могут иметь полезное поведение (например, реализация, использующая энергонезависимую ОЗУ, может на законных основаниях размещать переменные const в области памяти, доступной для записи, но содержимое которой останется, даже если модуль выключение и резервное копирование), и тот факт, что Стандарт не предъявляет никаких требований о том, как компиляторы обрабатывают код, который создает не const указатели на const память, не влияет на легитимность такого кода в реализациях, которые явно разрешают он . Однако даже на таких реализациях, вероятно, будет хорошей идеей заменить что-то вроде:

volatile const uint32_t action_count;

BYPASS_WRITE_PROTECT = 0x55; // Hardware latch which enables writing to
BYPASS_WRITE_PROTECT = 0xAA; // const memory if written with 0x55/0xAA
BYPASS_WRITE_PROTECT = 0x04; // consecutively followed by the bank number
*((uint32_t*)&action_count)++;
BYPASS_WRITE_PROTECT = 0x00; // Re-enable write-protection of const storage

с

void protected_ram_store_u32(uint32_t volatile const *dest, uint32_t dat)
{
    BYPASS_WRITE_PROTECT = 0x55; // Hardware latch which enables writing to
    BYPASS_WRITE_PROTECT = 0xAA; // const memory if written with 0x55/0xAA
    BYPASS_WRITE_PROTECT = 0x04; // consecutively followed by the bank number
    *((volatile uint32_t*)dest)=dat;
    BYPASS_WRITE_PROTECT = 0x00; // Re-enable write-protection of const storage
}
void protected_ram_finish(void) {}
...
protected_ram_store(&action_count, action_count+1);
protected_ram_finish();

Если компилятор будет склонен применять нежелательные «оптимизации» к коду, который записывает в const хранилище, перемещение «protected_ram_store» в отдельно скомпилированный модуль может послужить для предотвращения такой оптимизации. Это также может быть полезно, например, код должен перейти на оборудование, которое использует какой-то другой протокол для записи в память. Например, некоторые аппаратные средства могут использовать более сложные протоколы записи, чтобы минимизировать вероятность ошибочных записей. Наличие подпрограммы, чьей явной целью является запись в «нормально-постоянную» память, прояснит такие намерения.

0 голосов
/ 03 июня 2009

Поскольку это поведение не определено в спецификации, оно зависит от реализации, поэтому не переносимо, поэтому не является хорошей идеей.

Почему вы хотите изменить значение константы?

...