Как разыграть sockaddr_storage и избежать нарушения правил строгого наложения имен
-
07-07-2019 - |
Вопрос
Я использую руководство Beej по работе с сетями и столкнулся с проблемой псевдонимов. Он предлагает функцию для возврата адреса IPv4 или IPv6 определенной структуры:
1 void *get_in_addr( struct sockaddr *sa )
2 {
3 if (sa->sa_family == AF_INET)
4 return &(((struct sockaddr_in*)sa)->sin_addr);
5 else
6 return &(((struct sockaddr_in6*)sa)->sin6_addr);
7 }
Это приводит к тому, что GCC выдает ошибку строгого псевдонима для sa в строке 3. Насколько я понимаю, это потому, что я вызываю эту функцию следующим образом:
struct sockaddr_storage their_addr;
...
inet_ntop(their_addr.ss_family,
get_in_addr((struct sockaddr *)&their_addr),
connection_name,
sizeof connection_name);
Я предполагаю, что псевдонимы связаны с тем, что переменная they_addr
имеет тип sockaddr_storage
, а другой указатель другого типа указывает на ту же память. р>
Является ли лучший способ обойти эти залипания sockaddr_storage
, sockaddr_in
и sockaddr_in6
в объединение? Похоже, что это должно быть изношенной территорией в сети, я просто не могу найти хороших примеров с лучшими практиками.
Кроме того, если кто-то может точно объяснить, где возникает проблема с алиасами, я был бы очень признателен.
Решение
Я склонен делать это, чтобы заставить GCC правильно делать с type-punning , что явно разрешено для объединений:
/*! Multi-family socket end-point address. */
typedef union address
{
struct sockaddr sa;
struct sockaddr_in sa_in;
struct sockaddr_in6 sa_in6;
struct sockaddr_storage sa_stor;
}
address_t;
Другие советы
Я склонен делать это, чтобы заставить GCC правильно делать с типом-штамповкой, что явно разрешено для союзов
Я почти уверен, что это (неправильное) использование объединения не сработает (или только случайно) с GCC:
short type_pun2 (int i, int *pi, short *ps) { *pi = i; return *ps; } union U { int i; short s; }; short type_pun (int i) { U u; return type_pun2 (i, &u.i, &u.s); }
Правильный способ сделать это - memcpy
, а не union
.
Недавно у меня было похожее предупреждение о псевдониме в системе HPUX при попытке написать код для получения MAC-адреса компьютера
& amp; (((struct sockaddr_in *) addr) - > sin_addr)
жалуется на правила строгого наложения имен
Это код в некотором контексте
char ip[INET6_ADDRSTRLEN] = {0};
strucut sockaddr *addr
...
get addr from ioctl(socket,SOCGIFCONF...) call
...
inet_ntop(AF_INET, &(((struct sockaddr_in *)addr)->sin_addr),ip,sizeof ip);
Я преодолел предупреждение о псевдонимах, выполнив следующее
struct sockaddr_in sin;
memcpy(&sin,addr,sizeof(struct sockaddr));
inet_ntop(AF_INET, &sin.sin_addr,ip,sizeof ip);
И хотя это потенциально опасно, я добавил следующие строки перед ним
static_assert(sizeof(sockaddr)==sizeof(sockaddr_in));
Я не уверен, что это будет считаться плохой практикой, но это сработало и было кроссплатформенным для других * Nix-разновидностей и компиляторов
Проблема не имеет ничего общего с вызовом функции. Скорее, это с ((struct sockaddr_in *) sa) -> gt; sin_addr
. Проблема в том, что sa
является указателем одного типа, но вы приводите его к указателю другого типа и затем разыменовываете его. Это нарушает правило, называемое «строгим псевдонимом», согласно которому переменные разных типов никогда не могут иметь псевдонимов. В вашем случае псевдоним другого типа - это именно то, что вы хотите сделать.
Простое решение - отключить эту оптимизацию, которая позволяет использовать псевдонимы таким способом. В GCC этот флаг является -fno-strict-aliasing
.
Лучшее решение - использовать объединение, как упомянул Николай.
void *get_in_addr(struct sockaddr *sa)
{
union {
struct sockaddr *sa;
struct sockaddr_in *sa_in;
struct sockaddr_in6 *sa_in6;
} u;
u.sa = sa;
if (sa->sa_family == AF_INET)
return &(u.sa_in->sin_addr);
else
return &(u.sa_in6->sin6_addr);
}
Тем не менее, я не могу заставить GCC выдавать предупреждение при использовании вашего исходного кода, поэтому я не уверен, что это вас что-нибудь купит.