Может ли указатель создавать псевдоним беззнакового массива символов?

#include "stdio.h"  


/* array to store data receeived from CAN Bus */
unsigned char a[8] = {0xCD, 0xEF, 0x12, 0x34, 0x50, 0x00, 0x00, 0x00};

typedef struct {
    unsigned int a:12;
    unsigned int b:12;
    unsigned int c:12;
    unsigned int unused:28;
}test;

test *tptr;

int main(void)
{

    tptr = (test*)( (void*)&a); // is this line braking any aliasing rule

    if(tptr->a == 0xCDE)
    {
        printf("\n data received ok");

    }

    return 0;
}

Недавно я узнал о проблемах, связанных с псевдонимом указателя в C. Я хочу знать, нарушает ли приведенный выше код какие-либо правила. Может ли это привести к проблемам?

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


person Basha    schedule 18.06.2016    source источник
comment
Это не сайт с викторинами. Как вы думаете, почему это не так?   -  person too honest for this site    schedule 18.06.2016
comment
#include "stdio.h" необычно. Почему вы не используете #include <stdio.h>?   -  person melpomene    schedule 18.06.2016
comment
Я не знаю насчет псевдонимов, но я уверен, что строка tptr = ... имеет неопределенное поведение. Во-первых, не гарантируется, что B x; (A *)(void *)&x сделает что-либо разумное (вы только гарантированно сможете преобразовать void * обратно в исходный тип B *). Во-вторых, даже если это работает, структура может иметь требования к выравниванию.   -  person melpomene    schedule 18.06.2016
comment
В MSVC тест не прошел. Обратите внимание, что sizeof(test) это 12, а не 8. Возможно, вам нужно упаковать структуру.   -  person Weather Vane    schedule 18.06.2016
comment
В MSVC даже с упакованной структурой размер по-прежнему равен 12. И tptr->a == 0xFCD   -  person Weather Vane    schedule 18.06.2016
comment
tptr = (test *)a; не будет нарушать правило псевдонима. Но у вас есть все проблемы, упомянутые в других комментариях: выравнивание, заполнение, порядок байтов. Кроме того, поскольку сумма размеров ваших битовых полей равна 64, вам, вероятно, следует использовать uint64_t вместо unsigned int (если только unsigned int не является 64-битным в вашей системе).   -  person user3386109    schedule 18.06.2016
comment
Я работаю над 32-битным микроконтроллером Big Endian ARM.   -  person Basha    schedule 18.06.2016
comment
@user3386109 user3386109 действительно в MSVC с использованием неупакованного struct с членами типа uint64_t уменьшил его размер до 8, но все равно вывел 0xFCD.   -  person Weather Vane    schedule 18.06.2016
comment
@WeatherVane Да, если вы посмотрите на a как на 64-битное значение на процессоре с прямым порядком байтов, это 0x000000503412efcd, поэтому битовые поля будут 0xfcd, 0x12e и 0x034. У OP не должно быть этой проблемы на процессоре с прямым порядком байтов.   -  person user3386109    schedule 18.06.2016
comment
@user3386109 user3386109 Не согласен с тем, что вам, вероятно, следует использовать uint64_t. Битовое поле должно иметь тип, который является квалифицированной или неквалифицированной версией _Bool, signed int, unsigned int или какого-либо другого типа, определяемого реализацией. §6.7.2.1 5 Использование любого другого типа ограничивает переносимость и, возможно, вызывает UB. Лучше всего использовать unsigned, как это делал OP.   -  person chux - Reinstate Monica    schedule 19.06.2016


Ответы (1)


Это нарушает строгое сглаживание. Некоторые (более разумные) компиляторы делают вывод, что, очевидно, tptr должно быть разрешено использовать псевдоним a, но это не гарантируется стандартом. На самом деле, GCC не будет считать tptr псевдонимом a.

Другая проблема заключается в выравнивании a. В этом отношении x86 довольно толерантна, но многие другие архитектуры, включая ARM, — нет.

Рассмотрим (int)&a == 0xFFF02 и sizeof(int) == 4 в архитектуре, которая поддерживает выборку int только с адресов, которые сами кратны sizeof(int). Такой невыровненный доступ приведет к ошибке шины, если вы сделаете *(int*)&a или даже хуже программа обращается к неправильному местоположению.

Чтобы избежать неопределенного поведения, помните, что гарантируется, что доступ через unsigned char* возможен без ограничений выравнивания или псевдонимов, а не наоборот. Таким образом, используйте:

test buf;
can_recv((unsigned char*)&buf, sizeof buf);
if(tptr->a == 0xCDE)
{
    printf("\n data received ok");

}

Другая проблема с переносимостью заключается в том, что вы ожидаете, что это битовое поле будет упаковано определенным образом. Однако стандарт не гарантирует этого. Если вам нужно переносимое поведение, ограничьтесь интерфейсом битового поля (bitfield.b1 = 1) и не изменяйте его другими способами.

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

person a3f    schedule 18.06.2016
comment
Я бы посоветовал всем нормальным компиляторам признать, что акт явного приведения указателя должен служить тревожным звоночком. используя внешний тип. Обоснование описывает правило с точки зрения указателей, когда у компилятора нет причин ожидать псевдонимов; Я не видел никаких доказательств того, что правило когда-либо предназначалось для того, чтобы предположить, что компиляторы игнорируют явные и очевидные признаки алиасинга - авторы Стандарта, вероятно, думали, что это настолько очевидно, что не нуждается в указании. - person supercat; 23.06.2016