Почему я могу изменить локальную константную переменную с помощью указателей, но не глобальную в C?
Я хотел изменить значение константы с помощью указателей.
Рассмотрим следующий код
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; }
Как и ожидалось, значение константы изменяется.
- Swift 2 - UnsafeMutablePointer для объекта
- Сохранить int в массиве char?
- В обработчиках HTTP Go, почему ResponseWriter имеет значение, но запрашивает указатель?
- Возврат указателя локальной переменной C ++
- Как бесплатно знать, сколько бесплатно?
но когда я попробовал тот же код с глобальной константой, я получаю следующую ошибку времени выполнения. Открывается репортер аварийной ситуации 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 кодовых блоков.
Кто-нибудь может объяснить, что происходит?
- Что происходит в ОС, когда мы разыскиваем указатель NULL в C?
- Как сравнить указатели?
- Как включить динамический массив INSIDE a struct в C?
- Почему int указатель «++» увеличивается на 4, а не на 1?
- Использование указателей для удаления элемента из одноуровневого списка
- Изменение адреса, содержащегося в указателе с использованием функции
- Массивы - это указатели?
- C: различия между указателем char и массивом
Это в памяти только для чтения!
В принципе, ваш компьютер разрешает виртуальные физические адреса с использованием двухуровневой системы таблиц страниц. Наряду с этой грандиозной структурой данных появляется специальный бит, представляющий, читается ли страница. Это полезно, потому что пользовательские процессы, вероятно, не должны переписывать свою собственную сборку (хотя самомодифицирующийся код похож на classный). Конечно, они, вероятно, также не должны писать собственные постоянные переменные.
Вы не можете поместить переменную уровня «const» в память только для чтения, потому что она живет в стеке, где она ДОЛЖНА быть на странице чтения-записи. Тем не менее, компилятор / компоновщик видит ваш const, и делает вам одолжение, помещая его в постоянное запоминающее устройство (оно постоянное). Очевидно, что перезапись, которая вызовет всевозможные несчастья для ядра, выведет этот гнев на процесс, завершив его.
Это константа, и вы используете некоторые трюки, чтобы изменить ее в любом случае, так что результаты неопределенного поведения. Глобальная константа, вероятно, находится в постоянной памяти и поэтому не может быть изменена. Когда вы пытаетесь сделать это, вы получаете ошибку времени выполнения.
В стек создается постоянная локальная переменная, которая может быть изменена. Таким образом, вы избегаете изменения константы в этом случае, но это все равно может привести к странным вещам. Например, компилятор мог использовать значение константы в разных местах вместо самой константы, так что «изменение константы» не оказывает никакого эффекта в этих местах.
Выделение указательной константы в C и C ++ является только безопасным, если вы уверены, что указательная переменная изначально была неконстантной (и у вас просто есть указатель const). В противном случае он не определен, и в зависимости от вашего компилятора, фазы луны и т. Д., Первый пример также может сильно потерпеть неудачу.
Вы даже не должны ожидать, что значение будет изменено в первую очередь. Согласно стандарту, это неопределенное поведение. Это неправильно как с глобальной переменной, так и в первую очередь. Просто не делайте этого 🙂 Это могло бы сломаться иначе, или с местным и глобальным.
Здесь есть две ошибки. Первый:
int *ptr_to_const = &const_val;
что является нарушением ограничения в соответствии с C11 6.5.4 / 3 (предыдущие стандарты имели аналогичный текст):
Ограничения
Конверсии, которые include указатели, кроме случаев, когда это разрешено ограничениями 6.5.16.1, должны быть указаны с помощью явного приведения
Преобразование из const int *
в int *
не допускается ограничениями 6.5.16.1 (которые можно просмотреть здесь ).
Смутно, когда некоторые компиляторы сталкиваются с нарушением ограничения, они пишут «предупреждение» (или даже ничего вообще, в зависимости от коммутаторов) и притворяются, что вы написали что-то еще в своем коде и продолжаете. Это часто приводит к программам, которые не ведут себя так, как ожидал программист, или фактически не ведут себя предсказуемым образом. Почему компиляторы делают это? Меня бьет, но это, безусловно, вызывает бесконечный stream таких вопросов.
gcc, похоже, работает так, как если бы вы написали int *ptr_to_const = (int *)&const_val;
,
Этот fragment кода не является нарушением ограничения, потому что используется явный литой. Однако это приводит нас к второй проблеме. Строка *ptr_to_const = 20;
затем пытается записать объект const
. Это вызывает неопределенное поведение , соответствующий текст из Стандарта приведен в 6.7.3 / 6:
Если предпринимается попытка изменить объект, определенный с помощью типа, соответствующего const, с использованием значения lvalue с неконстантированным classом, поведение не определено.
Это правило – это семантика, а не ограничение, что означает, что стандарт не требует, чтобы компилятор выдавал какое-либо предупреждение или сообщение об ошибке. Программа просто неверна и может вести себя бессмысленно, с любыми странными симптомами, включая, но не ограничиваясь тем, что вы наблюдали.
Поскольку это поведение не определено в спецификации, оно специфично для реализации, поэтому оно не переносимо, поэтому не очень хорошая идея.
Почему вы хотите изменить значение константы?
Примечание: это предназначено для ответа. Можем ли мы изменить значение объекта, определенного с помощью const через указатели? который ссылается на этот вопрос как дубликат.
Стандарт не налагает никаких требований на то, что компилятор должен делать с кодом, который создает указатель на объект 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» в модуль скомпилированной компиляции может служить для предотвращения таких оптимизаций. Это также может быть полезно, например, код должен перейти на аппаратное обеспечение, которое использует какой-либо другой протокол для записи в память. Например, некоторые аппаратные средства могут использовать более сложные протоколы записи, чтобы минимизировать вероятность ошибочной записи. Наличие рутины, чья явная цель – записать в память «нормально-const», сделает такие намерения понятными.