Исправить для разыменования тип-караульный указатель нарушит строгое сглаживание
Я пытаюсь исправить два предупреждения при компиляции конкретной программы с помощью GCC. Предупреждения:
предупреждение: разыменованный тип
правила строгого сглаживания [-Wstrict-aliasing]
и два виновника:
unsigned int received_size = ntohl (*((unsigned int*)dcc->incoming_buf));
и
*((unsigned int*)dcc->outgoing_buf) = htonl (dcc->file_confirm_offset);
incoming_buf и outgoing_buf определяются следующим образом:
char incoming_buf[LIBIRC_DCC_BUFFER_SIZE];
char outgoing_buf[LIBIRC_DCC_BUFFER_SIZE];
Это кажется тонко отличным от других примеров этого предупреждения, которое я рассматривал. Я предпочел бы исправить проблему, а не отключать проверки с строгим сглаживанием.
Было много предложений по использованию союза - что может быть подходящим объединением для этого случая?
Ответы
Ответ 1
Прежде всего, давайте рассмотрим, почему вы получаете предупреждения о нарушении псевдонимов.
Правила псевдонимов просто говорят, что вы можете получить доступ только к объекту через его собственный тип, его тип с подписью/без знака или через тип символа (char
, signed char
, unsigned char
).
C говорит, что нарушение правил псевдонимов вызывает поведение undefined (так не надо!).
В этой строке вашей программы:
unsigned int received_size = ntohl (*((unsigned int*)dcc->incoming_buf));
хотя элементы массива incoming_buf
имеют тип char
, вы обращаетесь к ним как unsigned int
. Действительно, результат оператора разыменования в выражении *((unsigned int*)dcc->incoming_buf)
имеет тип unsigned int
.
Это нарушение правил сглаживания, потому что вы имеете право только на доступ к элементам массива incoming_buf
(см. сводку правил выше!) char
, signed char
или unsigned char
.
Обратите внимание, что у вас точно такая же проблема с псевдонимом у вашего второго виновника:
*((unsigned int*)dcc->outgoing_buf) = htonl (dcc->file_confirm_offset);
Вы получаете доступ к элементам char
outgoing_buf
через unsigned int
, поэтому это нарушение сглаживания.
Предлагаемое решение
Чтобы устранить проблему, вы можете попробовать иметь элементы ваших массивов, которые будут определены непосредственно в типе, к которому вы хотите получить доступ:
unsigned int incoming_buf[LIBIRC_DCC_BUFFER_SIZE / sizeof (unsigned int)];
unsigned int outgoing_buf[LIBIRC_DCC_BUFFER_SIZE / sizeof (unsigned int)];
(Кстати, ширина unsigned int
определяется реализацией, поэтому вы должны использовать uint32_t
, если ваша программа предполагает, что unsigned int
- 32-разрядная).
Таким образом, вы можете хранить объекты unsigned int
в своем массиве без нарушения правил псевдонимов, обратившись к элементу через тип char
, например:
*((char *) outgoing_buf) = expr_of_type_char;
или
char_lvalue = *((char *) incoming_buf);
EDIT:
Я полностью переработал свой ответ, в частности, объясню, почему программа получает предупреждения от псевдонимов от компилятора.
Ответ 2
Чтобы устранить проблему, не используйте каламбур и псевдоним! Единственным "правильным" способом чтения типа T
является выделение типа T
и заполнение его представления при необходимости:
uint32_t n;
memcpy(&n, dcc->incoming_buf, 4);
Вкратце: если вам нужно целое число, вам нужно сделать целое число. Там нет способа обманывать это по-испански.
Единственное преобразование указателя, которое вам разрешено (для целей ввода-вывода, в общем случае), относится к адресу существующей переменной типа T
как char*
, или, скорее, как указатель на первый элемент массива символов размером sizeof(T)
.
Ответ 3
union
{
const unsigned int * int_val_p;
const char* buf;
} xyz;
xyz.buf = dcc->incoming_buf;
unsigned int received_size = ntohl(*(xyz.int_val_p));
Упрощенное объяснение
1. Стандарт С++ утверждает, что вы должны попытаться самостоятельно выровнять данные, g++ - лишняя миля, чтобы генерировать предупреждения по этому вопросу.
2. Вы должны только попробовать, если вы полностью понимаете выравнивание данных в своей архитектуре/системе и внутри вашего кода (например, приведенный выше код является уверенным в Intel 32/64, выравнивание 1; Win/Linux/Bsd/Mac)
3. Единственная практическая причина использования вышеприведенного кода - избегать предупреждений компилятора, КОГДА и ЕСЛИ вы знаете, что вы делаете.
Ответ 4
Показ указателя на неподписанный, а затем обратно на указатель.
unsigned int received_size = ntohl (* ((unsigned *) ((без знака) dcc- > incoming_buf)));