2012-01-12 7 views
17

В отличие от C++, C не имеет понятия const_cast. То есть, не существует действительный способ преобразовать константный указатель квалифицированных неквалифицированному указатель:Является ли const-casting через undefined поведение соединения?

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

Во-первых: Является ли это приведение фактически неопределенное поведение?

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

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

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

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

+1

ли вы имеете в виду 'недействительным * д = (Недействительными *) p' в первый блок кода? – kennytm

+0

@KennyTM: Да - мне нужен явный приведение и/или это имеет значение? –

+0

Без неявного броска gcc предупреждает об отбрасывании квалификаторов. Но gcc все еще сообщает об ошибке («элемент инициализатора не является постоянным») даже при передаче. –

ответ

10

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

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

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

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

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

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

А (6.7.2.1/14):

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

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

+2

+1 для прямого ответа на вопрос OP :) – Kos

+1

Ницца! Значит, я не гарантирую, что это действительно будет делать то, что я думаю? –

+1

Ну, «definedatio defined» означает, что поставщик компилятора должен указать, что происходит. Однако вы не можете полагаться на это, работая на * all * компиляторы. – Lindydancer

3

Мое понимание того, что 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 Типа отборочных):

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

+1

Вы можете предположить, что я никогда ничего не мутирую. То есть, у меня есть не мутирующая операция, которая должна иметь дело с устаревшим изменяемым указателем. Я понимаю, что нарушение конституции - UB - мой вопрос в основном связан с манипуляцией указателем. В C++ я просто сказал бы: 'void * q = const_cast (p);' ... –

+0

@KerrekSB Не 'const_cast ' просто синтаксический сахар для '(void *)' (возможно, с некоторыми дополнительными проверками, но тем же поведение)? –

+2

@ChristianRau: Нет, это часть языка и система типов. Если вы это сделаете, вся система типов будет просто синтаксическим сахаром над ассемблером, поэтому здесь важно различие ... –

0

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

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

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

+0

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

+0

@Christian Rau: если вы знаете, что функция lagacy не изменяет данные, то явное приведение будет в порядке, как в комментарии KennyTM по исходному вопросу. – JeremyP

+1

@Kerrek SB C позволяет передавать указатель не const для параметра const. 'const char * p = q;' где q - char *. – JeremyP

3

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

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

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

§6.5.16.1/1: (Simple Assignment/контрсилы)

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

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

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

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

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


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

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

Отлично, спасибо! Как вы думаете, лучший способ написать C-код - это иметь явный прилив, когда это необходимо, и передать «-Wno-cast-qual» в GCC? –

+0

@KerrekSB: Я, безусловно, предпочитаю более строгие предупреждения о неявных методах, которые могут вызвать проблемы в будущем ☺, и, вероятно, лучше использовать «#pragma GCC diagnostic», чтобы свести к минимуму регион, в котором предупреждение отключено. – kennytm

+0

Несмотря на то, что это лучший ответ, поддержите ли вы мой ответ на более низкий ответ? Речь идет о репутации-мотивации-полезности, вы видите :-) –