2016-09-16 6 views
2
typedef struct { char ch; int num; } st_t; 
typedef union { char *pch; st_t *pst; } un_t; 

st_t st; 
st.ch = 's'; 

un_t un = { &st.ch }; 
*un.pch = 'u'; 

printf("%c\n", un.pst->ch); // expect: print the letter 'u' 

Насколько мне известно, адрес первого члена структуры и адрес самих структур одинаковы, поэтому un мог как точка st и st.ch в в то же время путем доступа к pst и pch. Тем не менее, стандарт C99 кажется, что он никогда явно не говорит the sizes of different types of pointers are identical. Тогда, моя забота о том, будет ли код поврежден, например, нарушать правила строгих правил псевдонимов, быть неопределенным поведением и т. Д.?Союза различных типов и размеров указателей

+1

Примечание: C99 не является текущим стандартом C. Это будет C11. Возможно, вы захотите прочитать о конверсиях указателей и т. Д. Одной из проблем, с которыми вы могли столкнуться, является нарушение правила правильного типа (aka strict aliasing), как вы упомянули. Это вызовет неопределенное поведение (UB не является проблемой сам по себе, но является результатом нарушения правила/ограничения. – Olaf

ответ

1

Линия:

un_t un = {&st.ch}; 
*un.pch = 'u'; 

присваивает адрес члена ч от структуры к члену PCH союза, и этот указатель затем используется для записи символа на этот адрес. Это совершенно правильно.

Существует проблема с линией, которая следует:

printf("%c\n", un.pst->ch); 

Член союза, кроме одного, который был последним хранится в, читается.
Следующее может быть ловушка представление :

un.pst 

(Цитируется: ISO/IEC 9899: 201x 6.2.6.1 Общие 5)
определенного объекта представлений не нужно представлять значение типа объекта. Если сохраненное значение объекта имеет такое представление и считывается выражением lvalue, которое имеет , не имеет типа символа, поведение не определено. Если такое представление создается побочным эффектом, который изменяет всю или любую часть объекта выражением lvalue, что не имеет типа символа, поведение не определено. 50) Такое представление называется представлением ловушки.

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

+0

Более распространенной проблемой для типичных 32/64 бит процессоров являются проблемы с выравниванием. – Olaf

+0

@Olaf Не в этом случае, поскольку указатель указывает на структуру. – 2501

+0

Хм, да, вы здесь верны. Я посмотрел на более общий случай. Выделение будет нарушать правило эффективного типа (я предпочитаю термин, используемый в стандарте). – Olaf

0

Хотя такая конструкция не соответствует стандарту относительно размеров указателей, на практике наиболее распространенные реализации (т. Е. Msvc, gcc/linux) используют одно и то же представление для указателей нефункционалов.

Так что на этих реализациях он должен работать так, как вы ожидали.

+0

Я бы очень внимательно следил за реализацией «** most **». Большинство реализаций для MCU, которые могут иметь все виды специальностей, например, для x86-16bit существуют разные ширины указателя, но нам не нужно так далеко. Выравнивание может отличаться для указателей. – Olaf

+0

@ Olaf Достаточно справедливо. Отредактировано до «наиболее распространенных реализаций», то есть то, с чем начинаются новички. – dbush

+0

Хм, новички в моем встроенном классе могли бы начать с PIC, C51 и т. д .;-) (да, это было nit-picking, и я остановлюсь прямо здесь ;-) – Olaf

0

Стандарт C пытается определить характеристики, функции и гарантии, которые необходимы для всех соответствующих реализаций; он не пытается описать большинство полезных характеристик и функций, которые являются общими для 99% реализаций, но которые не требуются.

На подавляющем большинстве реализаций, все указатели данных используют такое же представление, поэтому данное thingType *foo=xxx; void *vfoo = foo;, как foo и vfoo будут держать один и тот же набор бит. До появления агрессивных оптимизаторов, такие реализации будет вполне целесообразно разрешить любые виды данных указатель на доступ с помощью void**, поэтому можно было бы написать такие функции, как:

int realloc_if_possible(void **p, int newsize) 
{ 
    void *temp = realloc(*p, newsize); 
    if (!temp) return -1; 
    *p = temp; 
    return 0; 
} 

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

+0

Как насчет объединения, содержащего sometype * и void *? Соответствует ли этот тип punning стандарту, поскольку void * должен соответствовать любым другим типам указателей? –

+1

@KevinDong: Это относится к категории поведения, которые поддерживаются 99% реализаций, но которые стандарт не требует. – supercat