Почему разрешено перезаписывать константную переменную с помощью указателя на нее с помощью memcpy?

Почему разрешено изменять константную переменную с помощью указателя на нее с помощью memcpy?

Этот код:

const int i=5;
int j = 0;
memcpy(&j, &i, sizeof(int));
printf("Source: i = %d, dest: j = %d\n", i,j);

j = 100;
memcpy(&i, &j, sizeof(int));
printf("Source: j = %d, dest: i = %d\n", j,i);
return 0;

скомпилировано только с предупреждением:

предупреждение: передача аргумента 1 «memcpy» отбрасывает квалификатор «const» из целевого типа указателя [включено по умолчанию]

Но все работало нормально и изменило значение константной переменной.


person Chris    schedule 20.03.2013    source источник
comment
const не так мощен, как вы могли бы надеяться в C. Нет гарантии, что переменная будет доступна только для чтения.   -  person Randy Howard    schedule 20.03.2013


Ответы (4)


Вопрос спрашивает почему. Вот почему:

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

const останавливает изменение значения const с присваиванием (или приращением и т. д.). Этот тип мутации — единственные операции, которые он может гарантировать, что вы не сможете выполнять над константой.

Другой способ взглянуть на это — разделение статического контекста (т. е. во время компиляции) и контекста времени выполнения. Когда вы компилируете фрагмент кода, который может, например, присваивать значение переменной, язык может сказать «это недопустимо, это константа», и это ошибка компиляции. После этого код компилируется в исполняемый файл, и тот факт, что это const, теряется. Объявления переменных (и остальная часть языка) записываются как входные данные для компилятора. После того, как он скомпилирован, код не имеет значения. Вы можете сделать логическое доказательство в своем компиляторе, чтобы сказать, что consts не изменились. Скомпилированная программа запускается, и во время компиляции мы знаем, что создали программу, не нарушающую правил.

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

Тем не менее, указатели — это то, как мы получаем динамическое поведение и структуры данных, и они необходимы для всего, кроме самого тривиального кода.

(Вышеизложенное подлежит множеству предостережений, т.е. эвристика кода, более сложная шина статического анализа в целом верна для ванильного компилятора.)

person Joe    schedule 20.03.2013
comment
Конечно. Но этот вопрос С. - person Joe; 20.03.2013
comment
Указан язык C, а не C++. - person Randy Howard; 20.03.2013

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

C11 (n1570), § 6.7.3 Определители типов

Если предпринимается попытка изменить объект, определенный с типом, уточненным константой, путем использования lvalue с типом, не уточненным константой, поведение не определено.

Ничто не заставляет компилятор выдавать диагностическое сообщение.

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

Компилятор не может легко сказать, на что указывает указатель в данной функции. Это возможно с некоторыми инструментами статического анализа, которые выполняют анализ указателя. Однако его сложно реализовать, и было бы глупо заносить его в стандарт.

person md5    schedule 20.03.2013
comment
Это не отвечает на вопрос. - person Joe; 20.03.2013
comment
@Joe: Почему разрешено перезаписывать константную переменную? Потому что ничто не заставляет компилятор выдавать диагностическое сообщение. Или, может быть, вы говорите о том, почему стандарт такой? - person md5; 20.03.2013
comment
Хм. Просто мне это показалось не прямым ответом на вопрос «почему», а скорее комментарием о том, что это так. Не проблема, но я просто не видел в этом ответа, на мой взгляд. - person Joe; 20.03.2013
comment
@Joe: я добавил небольшой отрывок о том, почему. Надеюсь, теперь это выглядит как настоящий ответ (кстати, мой английский определенно плохой). - person md5; 20.03.2013

Причина в том, что язык C позволяет неявно привести любой тип указателя к типу void* и обратно. Он разработан таким образом, потому что указатели void используются для универсального программирования.

Таким образом, компилятор C не может остановить компиляцию вашего кода, даже если в этом случае программа вызывает неопределенное поведение. Однако хороший компилятор выдаст предупреждение, как только вы неявно попытаетесь отбросить квалификатор const.

C++ имеет «более строгую типизацию», чем C, а это означает, что для компиляции этого кода потребуется явное приведение типа указателя. Это один из недостатков языка C, который фактически исправлен в C++.

person Lundin    schedule 20.03.2013

Хотя «официально» он не определен, на самом деле он очень определен — вы измените значение переменной const. Что поднимает вопрос, почему это const для начала.

person Community    schedule 20.03.2013