Подтвердить что ты не робот

Является ли const-casting с помощью объединения undefined поведения?

В отличие от С++, C не имеет понятия const_cast. То есть, не существует допустимого способа преобразования указателя с константой в неквалифицированный указатель:

void const * p;
void * q = p;    // not good

Во-первых: действительно ли это поведение undefined?

В любом случае GCC предупреждает об этом. Чтобы сделать "чистый" код, требующий const-cast (то есть, где я могу гарантировать, что я не буду мутировать содержимое, но все, что у меня есть, является изменяемым указателем), я видел следующий трюк преобразования:

typedef union constcaster_
{
    void * mp;
    void const * cp;
} constcaster;

Использование: u.cp = p; q = u.mp;.

Каковы правила языка C при отбрасывании констебля через такой союз? Мои знания о C только очень неоднородны, но я слышал, что C гораздо более мягко относится к объединенному доступу, чем к С++, поэтому, хотя у меня плохое представление об этой конструкции, я бы хотел аргумент из стандарта (C99, я полагаю, хотя, если это изменилось на C11, это будет полезно знать).

4b9b3361

Ответ 1

Эта реализация определена, см. C99 6.5.2.3/5:

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

Обновление: @AaronMcDaid прокомментировал, что это может быть хорошо определено в конце концов.

В стандарте указано следующее 6.2.5/27:

Аналогично, указатели на квалифицированные или неквалифицированные версии совместимых типы должны иметь одинаковое представление и выравнивание requirements.27)

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

И (6.7.2.1/14):

Указатель на объект объединения, соответствующим образом преобразованный, указывает на каждый из его членов (или если член является битовым полем, то к блоку, в котором он проживает) и наоборот.

Можно сделать вывод, что в этом конкретном случае есть только место для одного способа доступа к элементам в объединении.

Ответ 2

Я понимаю, что UB может возникнуть, только если вы попытаетесь изменить объект, объявленный const.

Итак, следующий код не UB:

int x = 0;
const int *cp = &x;
int *p = (int*)cp;
*p = 1; /* OK: x is not a const object */

Но это UB:

const int cx = 0;
const int *cp = &cx;
int *p = (int*)cp;
*p = 1; /* UB: cx is const */

Использование союза вместо броска не должно иметь никакого значения здесь.

Из спецификаций C99 (6.7.3 Типовых классификаторов):

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

Ответ 3

Инициализация, конечно же, не вызовет UB. Преобразование между квалифицированными типами указателей явно разрешено в п. 6.3.3.3/2 (n1570 (C11)). Это использование содержимого в этом указателе, которое вызывает UB (см. @rodrigo).

Однако для преобразования a void* в const void* требуется явное преобразование, потому что ограничение простого назначения по-прежнему требует, чтобы весь определитель на LHS появился в RHS.

§6.7.9/11:... Начальное значение объекта - это выражение (после преобразования); применяются те же ограничения типа и преобразования, что и для простого присваивания, при этом тип скаляра является неквалифицированной версией его объявленного типа.

§6.5.16.1/1: (Простые присвоения/ограничения)

  • ... оба операнда указатели на квалифицированные или неквалифицированные версии совместимых типов, а тип слева - все определители типа, на которые указывает справа;
  • ... один операнд - это указатель к типу объекта, а другой - указатель на квалифицированную или неквалифицированную версию void, а тип, на который указывает левый, имеет все квалификаторы типа, указывающего на по праву;

Я не знаю, почему gcc просто дает предупреждение.


И для профсоюзного трюка, да, это не UB, но все же результат, вероятно, не указан.

§6.5.2.3/3 fn 95. Если элемент, используемый для чтения содержимого объекта объединения, не совпадает с элементом, который последний раз использовался для хранения значения в объекте, соответствующий часть объектного представления значения переинтерпретируется как представление объекта в новом типе, как описано в 6.2.6 (процесс, иногда называемый "тип punning" ). Это может быть ловушечное представление.

§6.2.6.1/7: когда значение хранится в члене объекта типа объединения, байты представления объекта, которые не соответствуют этому элементу, но соответствуют другим члены принимают неопределенные значения. (* Примечание: см. Также §6.5.2.3/6 для исключения, но оно здесь не применяется)


Соответствующие разделы в n1124 (C99) являются

  • C11 §6.3.2.3/2 = C99 §6.3.2.3/2
  • C11 §6.7.9/11 = C99 §6.7.8/11
  • C11 §6.5.16.1/1 = C99 §6.5.16.1/1
  • C11 §6.5.2.3/3 fn 95 = отсутствует ( "тип punning" не отображается в C99)
  • C11 §6.2.6.1/7 = C99 §6.2.6.1/7

Ответ 4

Не бросайте его вообще. Это указатель на const, что означает, что попытка изменить данные не допускается, и во многих реализациях это приведет к сбою программы, если указатель указывает на немодифицируемую память. Даже если вы знаете, что memmory можно изменить, могут быть другие указатели на него, которые не ожидают его изменения, например. если он является частью хранилища логически неизменяемой строки.

Предупреждение существует по уважительной причине.

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