Почему я могу изменить локальную константную переменную с помощью указателей, но не глобальную в 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; } 

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

но когда я попробовал тот же код с глобальной константой, я получаю следующую ошибку времени выполнения. Открывается репортер аварийной ситуации 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 кодовых блоков.

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

Это в памяти только для чтения!

В принципе, ваш компьютер разрешает виртуальные физические адреса с использованием двухуровневой системы таблиц страниц. Наряду с этой грандиозной структурой данных появляется специальный бит, представляющий, читается ли страница. Это полезно, потому что пользовательские процессы, вероятно, не должны переписывать свою собственную сборку (хотя самомодифицирующийся код похож на 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», сделает такие намерения понятными.

  • Есть ли преимущества перехода по указателю на передачу по ссылке в C ++?
  • Как использовать глобальный var для файлов в пакете?
  • Что является результатом NULL + int?
  • Как узнать, указывает ли указатель на кучу или стек?
  • Существуют ли платформы, где указатели на разные типы имеют разные размеры?
  • Имеют ли эти утверждения о указателях одинаковый эффект?
  • Почему звездочка перед именем переменной, а не после типа?
  • Что такое указатель void и что такое пустой указатель?
  • Функция не меняет пройденный указатель C ++
  • Одномерный доступ к многомерному массиву: хорошо ли оно определено?
  • Объявление типа указателей?
  • Interesting Posts

    Многостраничный pendrive в Windows 7

    Как я могу получить поведение Fbox 33.x Flexbox в FF 34.x?

    Могу ли я добавить свою внутреннюю ссылку в окно сохранения / открытия окна?

    Установите равную ширину столбцов в макете таблицы в Android

    Лучший способ проанализировать аргументы командной строки в C #?

    Как реализовать многопользовательский вход пользователя с использованием идентификатора ASP.NET

    Инъекция зависимостей против заводского шаблона

    Настройка Quartz.net на веб-сайте asp.net

    API GoogleMaps v3 Создайте только 1 маркер при щелчке

    Автоматически запускать локальные уведомления ежедневно в динамическое время, заданное в массивах. objective ios

    Метод MVC MVC для разных controllerов

    Ошибка CreateProcess = 206. Имя файла или расширение слишком велико при запуске метода main ()

    android: ViewPager и HorizontalScrollVIew

    Проверьте, равны ли два списка

    Как настроить ноутбук Windows 7 как точку доступа Bluetooth?

    Давайте будем гением компьютера.