Проблема с этим конкретным вопросом и ответом Звола заключается в том, что они объединяют типизацию и строгое наложение.Ответ Зволя правильный для этого конкретного варианта использования из-за типа, используемого для инициализации структуры;но не в общем случае и не в отношенииstruct sockaddr
POSIX-типы, как можно прочитать из ответа, подразумеваемого.
Для определения типов между структурами с общими начальными членами все, что вам нужно сделать, это объявить (не использовать!) Объединение этих структур,и вы можете безопасно получить доступ к общим членам через указатель любого из типов структур.Это явно разрешенное поведение начиная с ANSI C 3.3.2.3, включая C11 6.5.2.3p6 (ссылка на черновик n1570).
Если реализация содержит объединение всех struct sockaddr_
структурвидимый для приложений пользовательского пространства, ответ zwol OP ссылки на OP, на мой взгляд, вводит в заблуждение, если считать, что подразумевается, что поддержка структуры struct sockaddr
требует чего-то нестандартного от компиляторов.(Если вы определите _GNU_SOURCE
, glibc определит такое объединение как struct __SOCKADDR_ARG
, содержащее анонимное объединение всех таких типов. Однако glibc предназначен для компиляции с использованием GCC, поэтому у него могут быть другие проблемы.)
Строгое псевдонимы - это требование, чтобы параметры функции не ссылались на одно и то же хранилище (память).Например, если у вас
int i = 0;
char *iptr = (char *)(&i);
int modify(int *iptr, char *cptr)
{
*cptr = 1;
return *iptr;
}
, то вызов modify(&i, iptr)
является строгим нарушением псевдонимов.Тип punning в определении iptr
является случайным и фактически разрешен (поскольку вы можете использовать тип char
для проверки представления хранилища любого типа; C11 6.2.6.1p4 ).
Вот правильный пример перетаскивания типов, позволяющий избежать строгих проблем с псевдонимами:
struct item {
struct item *next;
int type;
};
struct item_int {
struct item *next;
int type; /* == ITEMTYPE_INT */
int value;
};
struct item_double {
struct item *next;
int type; /* == ITEMTYPE_DOUBLE */
double value;
};
struct item_string {
struct item *next;
int type; /* == ITEMTYPE_STRING */
size_t length; /* Excluding the '\0' */
char value[]; /* Always has a terminating '\0' */
};
enum {
ITEMTYPE_UNKNOWN = 0,
ITEMTYPE_INT,
ITEMTYPE_DOUBLE,
ITEMTYPE_STRING,
};
Теперь, если в той же области виден следующий союз, мы можем набрать пункт между указателямик указанным выше типам структур и совершенно безопасно получить доступ к элементам next
и type
:
union item_types {
struct item any;
struct item_int i;
struct item_double d;
struct item_string s;
};
Для других (не общих) элементов мы должны использовать тот же тип структуры, который использовалсяинициализировать структуру.Вот почему существует поле type
.
В качестве примера такого полностью безопасного использования рассмотрим следующую функцию, которая печатает значения в списке элементов:
void print_items(const struct item *list, FILE *out)
{
const char *separator = NULL;
fputs("{", out);
while (list) {
if (separator)
fputs(separator, out);
else
separator = ",";
if (list->type == ITEMTYPE_INT)
fprintf(out, " %d", ((const struct item_int *)list)->value);
else
if (list->type == ITEMTYPE_DOUBLE)
fprintf(out, " %f", ((const struct item_double *)list)->value);
else
if (list->type == ITEMTYPE_STRING)
fprintf(out, " \"%s\"", ((const struct item_string *)list)->value);
else
fprintf(out, " (invalid)");
list = list->next;
}
fputs(" }\n", out);
}
Примечаниечто я использовал то же имя value
для поля значения, просто потому, что я не придумал ничего лучшего;они не обязательно должны быть одинаковыми.
Обтекание по типу происходит в операторах fprintf()
и действует в том и только в том случае, если 1) структуры были инициализированы с использованием структур, соответствующих полю type
, и2) union item_types
виден в текущей области видимости.
Ни у одного из текущих компиляторов C, которые я пробовал, нет проблем с приведенным выше кодом, даже на экстремальных уровнях оптимизации, которые нарушают некоторые аспекты стандартного поведения.(Я не проверял MSVC, но это действительно компилятор C ++, который также может компилировать большую часть кода C. Однако я был бы удивлен, если бы у него были какие-либо проблемы с приведенным выше кодом.)