Можем ли мы изменить значение объекта, определенного с помощью const через указатели?
#include <stdio.h>
int main()
{
const int a = 12;
int *p;
p = &a;
*p = 70;
}
Это будет работать?
11 ответов
Это "неопределенное поведение", означающее, что на основе стандарта вы не можете предсказать, что произойдет, когда вы попробуете это. Он может делать разные вещи в зависимости от конкретной машины, компилятора и состояния программы.
В этом случае чаще всего случается так, что ответом будет "да". Переменная, const или нет, это просто место в памяти, и вы можете нарушить правила константности и просто перезаписать ее. (Конечно, это вызовет серьезную ошибку, если какая-то другая часть программы зависит от того, что ее константные данные постоянны!)
Однако в некоторых случаях - чаще всего для const static
data - компилятор может поместить такие переменные в область памяти, доступную только для чтения. Например, MSVC обычно помещает постоянные статические целые числа в сегмент.text исполняемого файла, что означает, что операционная система выдает ошибку защиты, если вы попытаетесь записать ее, и программа завершится сбоем.
В какой-то другой комбинации компилятора и машины может произойти что-то совершенно другое. Единственное, что вы можете точно предсказать, это то, что этот шаблон будет раздражать любого, кто будет читать ваш код.
Это неопределенное поведение. Доказательство:
/* program.c */
int main()
{
const int a = 12;
int* p;
p = &a;
*p = 70;
printf("%d\n", a);
return 0;
}
gcc program.c
и запустить его. Выход будет 70 (ГЦК 4.3)
Затем скомпилируйте это так:
gcc -O2 program.c
и запустить его. Вывод будет равен 12. Когда он выполнит оптимизацию, компилятор предположительно загружает 12 в регистр и не удосуживается загрузить его снова, когда ему нужно получить доступ к printf, потому что он "знает", что не может измениться.
Модификация const
квалифицированный объект через указатель вызывает неопределенное поведение, и таков результат. Вполне вероятно, что это то, что вы ожидаете, т.е. предыдущее значение без изменений, если оно было помещено в .text
, так далее.
Это действительно работает с GCC. Это не понравилось, хотя:
test.c: 6: предупреждение: назначение отбрасывает квалификаторы из целевого типа указателя
Но значение изменилось при исполнении. Я не буду указывать на очевидное нет-нет...
Да, вы можете сделать это с помощью такого кода. но код не применяется, когда когда a
глобален (программа, скомпилированная gcc, дала мне segmentation fault
.)
Вообще говоря, в любимом C вы почти всегда можете найти способ взломать вещи, которые не должны быть изменены или выставлены. Const здесь быть примером.
Но думая о бедняге (возможно, о себе через 6 месяцев), мы поддерживаем наш код, я часто решаю не делать этого.
Здесь тип указателя p
является int*
, которому присваивается значение типа const int*
(&a
=> адрес const int
переменная).
Неявное приведение исключает константность, хотя gcc выдает предупреждение (обратите внимание, что это во многом зависит от реализации).
Поскольку указатель не объявлен как const
значение может быть изменено с помощью такого указателя.
если указатель будет объявлен как const int* p = &a
вы не сможете сделать *p = 70
,
Этот код содержит нарушение ограничения:
const int a = 12;
int *p;
p = &a;
Нарушено ограничение: C11 6.5.16.1/1 "Простое назначение"; если оба операнда являются указателями, тогда тип, на который указывает левый, должен иметь все квалификаторы типа, на который указывает правый. (И типы, без квалификаторов, должны быть совместимы).
Таким образом, ограничение нарушается, потому что &a
имеет тип const int *
, у которого есть const
в качестве классификатора; но этот классификатор не появляется в типе p
который int *
,
Компилятор должен выдавать диагностику и может не генерировать исполняемый файл. Поведение любого исполняемого файла будет полностью неопределенным, поскольку программа не соответствует правилам языка.
Вы не можете изменить значение постоянной переменной, используя указатель на нее. Этот тип указателя называется Pointer to a constant
,
Есть еще одна концепция, которая называется Constant Pointer
, Это означает, что как только указатель указывает на область памяти, вы не можете сделать так, чтобы он указывал на другую область.
Плохая, плохая идея.
Кроме того, поведение зависит от платформы и реализации. Если вы работаете на платформе, где константа хранится в недоступной для записи памяти, это, очевидно, не сработает.
И с какой стати вы хотите этого? Либо обновите константу в вашем источнике, либо сделайте ее переменной.
Проблема с изменением значения переменной заключается в том, что компилятор этого не ожидает. Рассмотрим этот код:
const int a = 12;
int * p = &a;
*p = 70;
printf("%d\n", a);
Почему компилятор прочитал последний оператор? Компилятор знает, что это
const int a = 12;
int * p = &a;
*p = 70;
printf("%d\n", 12);
Это может привести к странным проблемам. Например, код может работать должным образом в сборках отладки без оптимизации, но он не будет работать в сборках выпуска с оптимизацией.
На самом деле хороший оптимизатор может преобразовать весь код в это:
printf("%d\n", 12);
Как и весь предыдущий код, компилятор не видит никакого эффекта. Отсутствие неэффективного кода также не повлияет на всю программу.
С другой стороны, достойный компилятор распознает, что ваш код неисправен, и предупредит вас, поскольку
int * p = &a;
на самом деле неправильно. Правильно было бы:
const int * p = &a;
в качестве
Чтобы избавиться от предупреждения, вы должны использовать:
int * p = (int *)&a;
И даже лучший компилятор распознает, что это приведение нарушает обещание, и проинструктирует оптимизатор не обрабатывать
Как видите, качество, возможности и настройки компилятора в конечном итоге решают, какого поведения вы можете ожидать. Это означает, что один и тот же код может демонстрировать разное поведение на разных платформах или при использовании разных компиляторов на одной платформе.
Если бы в стандарте C было определено поведение для этого случая, все компиляторы должны были бы его реализовать, и независимо от того, что определено в стандарте, его было бы трудно реализовать, что ложилось бы огромным бременем на всех, кто хочет написать компилятор. Даже если бы в стандарте было сказано: «Это запрещено», всем компиляторам пришлось бы выполнять сложный анализ потока данных, чтобы обеспечить соблюдение этого правила. Так что стандарт просто не определяет этого. Он определяет, что
Да, вы можете изменить значение постоянной переменной.
Попробуйте этот код:
#include <stdio.h>
int main()
{
const int x=10;
int *p;
p=(int*)&x;
*p=12;
printf("%d",x);
}