Исправление для разыменования указателя с типом, которое нарушает строгий псевдоним.

Я пытаюсь исправить два предупреждения при компиляции конкретной программы с использованием 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];

Это кажется немного отличным от других примеров того предупреждения, которое я изучал. Я бы предпочел исправить проблему, а не отключать проверку строгого алиасинга.

Было много предложений использовать союз - какой союз может быть подходящим для этого случая?


person BlankFrank    schedule 11.01.2012    source источник
comment
Интересно ... строгое алиасинг не должен применяться к char*. Или я что-то упускаю?   -  person Mysticial    schedule 11.01.2012
comment
@Mysticial Да, вам не хватает нарушения псевдонима, когда к объекту типа T1 обращаются с lvalue типа T2 и T2 равно char, но когда T1 равно char и T2 не относится к подписанному / неподписанному варианту char , есть нарушение псевдонима.   -  person ouah    schedule 11.01.2012
comment
@Mysticial: Вы неправильно поняли!   -  person Kerrek SB    schedule 11.01.2012


Ответы (8)


Прежде всего, давайте разберемся, почему вы получаете предупреждения о нарушении псевдонима.

Правила наложения просто говорят, что вы можете получить доступ к объекту только через его собственный тип, его вариантный тип со знаком / без знака или через символьный тип (char, signed char, unsigned char).

C говорит, что нарушение правил псевдонима вызывает неопределенное поведение (так не надо!).

В этой строке вашей программы:

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;

or

char_lvalue = *((char *) incoming_buf);

РЕДАКТИРОВАТЬ:

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

person ouah    schedule 11.01.2012

Чтобы решить эту проблему, не используйте каламбур и псевдонимы! Единственный «правильный» способ чтения типа T - выделить тип T и при необходимости заполнить его представление:

uint32_t n;
memcpy(&n, dcc->incoming_buf, 4);

Вкратце: если вам нужно целое число, вам нужно сделать целое число. Невозможно обмануть это оправданным языком способом.

Единственное разрешенное преобразование указателя (обычно для целей ввода-вывода) - это обрабатывать адрес существующей переменной типа T как char*, или, скорее, как указатель на первую элемент массива символов размером sizeof(T).

person Kerrek SB    schedule 11.01.2012
comment
Я не уверен, что sizeof(uint32_t) гарантированно будет равно 4, поэтому вам, возможно, придется отрегулировать memcpy - person OmarL; 24.08.2020

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. Стандарт c ++ гласит, что вы должны попытаться выровнять данные самостоятельно, g ++ делает все возможное, чтобы генерировать предупреждения по этому вопросу. 2. Вы должны пытаться это сделать только в том случае, если вы полностью понимаете выравнивание данных в вашей архитектуре / системе и внутри вашего кода (например, приведенный выше код надежен на Intel 32/64; выравнивание 1; Win / Linux / Bsd / Mac) 3. Единственная практическая причина использовать приведенный выше код - избежать предупреждений компилятора, КОГДА и ЕСЛИ вы знаете, что делаете.

person Real Name    schedule 06.09.2016

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

unsigned int* buf = (unsigned int*)dcc->incoming_buf;
unsigned int received_size = ntohl (*buf);
person Oleg Oleg    schedule 27.02.2019
comment
Отбрасывать предупреждения - это плохо. Этот код ничего не делает для нарушения строгого псевдонима, а также может нарушать ограничения по выравниванию. - person Andrew Henle; 20.04.2021

Если можно, ИМХО, в этом случае проблема заключается в дизайне API-интерфейсов ntohl и htonl и связанных с ними функций. Их нельзя было записывать как числовой аргумент с числовым возвратом. (и да, я понимаю точку макрооптимизации). Они должны были быть спроектированы как сторона «n», являющаяся указателем на буфер. Когда это будет сделано, вся проблема исчезнет, ​​и процедура будет точной, независимо от того, какой порядок байтов принимает хост. Например (без попытки оптимизации):

inline void safe_htonl(unsigned char *netside, unsigned long value) {
    netside[3] = value & 0xFF;
    netside[2] = (value >> 8) & 0xFF;
    netside[1] = (value >> 16) & 0xFF;
    netside[0] = (value >> 24) & 0xFF;
};
person Henri Socha    schedule 16.03.2018
comment
Если бы Стандарт включал стандартный набор подпрограмм выборки и заполнения с прямым порядком байтов и обратным порядком байтов, такие процедуры могли бы иметь полезное поведение, определяемое в терминах октетов, даже на машинах, где CHAR_BIT не равно восьми, что повысит переносимость сетевого кода. на таких машинах. - person supercat; 21.02.2019

Недавно я обновил проект с GCC 6 до GCC 9 и начал видеть это предупреждение. Проект реализован на 32-битном микроконтроллере, и я создал структуру для доступа к отдельным байтам 32-битного машинного регистра:

struct TCC_WEXCTRL_t
{
    byte    OTMX;
    byte    DTIEN;
    byte    DTLS;
    byte    DTHS;
};

а затем закодированы:

((TCC_WEXCTRL_t *)&TCC0->WEXCTRL)->DTLS = PwmLoDeadTime;

что привело к предупреждению в новом компиляторе. Я обнаружил, что могу устранить предупреждение, объединив мою структуру в объединение с исходным типом:

union TCC_WEXCTRL_t
{
    TCC_WEXCTRL_Type std;
    struct  
    {
        byte    OTMX;
        byte    DTIEN;
        byte    DTLS;
        byte    DTHS;
    };    
};

где TCC_WEXCTRL_Type - это тип элемента WEXCTRL, как указано в файлах заголовков производителя.

Я не уверен, считается ли это полностью совместимым исправлением, или GCC просто не может его отловить. Если бы это не сработало (или было бы поймано в другом обновлении GCC), я бы перешел к использованию объединения типов указателей, как описано в этом потоке Real Name.

person DosMan    schedule 13.10.2020

C cast не сработал, но reinterpret_cast ‹› помог мне в подобной ситуации.

person Evgeny Yashin    schedule 17.12.2020

Привести указатель к беззнаковому, а затем обратно к указателю.

беззнаковый интервал полученный_размер = ntohl (* ((беззнаковый *) ((беззнаковый) dcc-> incoming_buf)));

person archimedes    schedule 10.07.2015