Является ли const-casting через union неопределенным поведением? - PullRequest
17 голосов
/ 12 января 2012

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

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

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

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

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

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

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

Ответы [ 4 ]

10 голосов
/ 12 января 2012

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

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

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

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

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

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

И (6.7.2.1/14):

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

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

3 голосов
/ 12 января 2012

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

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

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

§6.5.16.1 / 1 : (Простое назначение/ Contraints)

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

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


И для трюка с объединением, да, это не UB, но результат, вероятно, не определен.

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

§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 = отсутствует («тип пробивки» отсутствует в C99)
  • C11 §6.2.6.1 / 7 = C99 §6.2.6.1 / 7
3 голосов
/ 12 января 2012

Я понимаю, что 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 с неконстантным типом, поведение не определено.

0 голосов
/ 12 января 2012

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...