Доступ к массиву из структуры и приведение его к указателю

struct message {
  uint8_t start;
  uint16_t length;
  uint8_t data[10];
  uint8_t checkSum;
} __attribute__((packed));

struct message devices[10];

void request(struct message *msg) {
  struct request *req = (struct request *)&msg->data;
  req->operation = 1;
  req->requesterAddress = MASTER_ADDRESS;      
}

request(&devices[0]);

Мой вопрос: почему амперсанд в «& msg-> data»?

Насколько я понимаю, функция «запрос» получает указатель «msg» на структуру. msg->data извлекает указатель «данные» из структуры, на которую указывает «msg» («данные» — это массив), а затем приводит к другому типу указателя (запрос структуры *).

Так что эта часть должна быть (struct request *)msg->data; Итак, почему амперсанд (&)?


person Jownas Bugado    schedule 10.04.2017    source источник
comment
Примечание. Приведение не только неверно, но и присваивание также вызывает неопределенное поведение (фактически доступ после присваивания). Ваш код нарушает правило эффективного типа.   -  person too honest for this site    schedule 10.04.2017
comment
msg-›data извлекает данные указателя — нет, он обращается к данным массива. Массивы просто распадаются на указатели в 95% случаев использования.   -  person StoryTeller - Unslander Monica    schedule 11.04.2017
comment
Помимо валидности, &msg->data выражается как тип указателя uint8_t (*)[10], а msg->data выражается как uint8_t*. Не то, чтобы это имело значение, так как вы выбрасываете тип независимо и приводите к желаемому выравниванию struct request *.   -  person WhozCraig    schedule 11.04.2017
comment
Приведение присутствует, потому что неприведенный тип — uint8_t (*)[10], который не совпадает с struct request * (с большим отрывом). & не нужен; тогда вы принуждаете uint8_t * к struct request *, что попадает в деликатную область по поводу «является ли uint8_t типом персонажа», потому что псевдонимы между типами символов и другими допустимы (но многие другие преобразования — нет). И упаковка делает этот бросок еще более проблематичным. Ожидайте сбоев и других странных действий.   -  person Jonathan Leffler    schedule 11.04.2017


Ответы (1)


Ну, этот код не C, потому что __attribute__((packed)) действителен только в gcc и некоторых других конкретных реализациях, но определенно не существует в стандартном C.

Но при условии sizeof(struct message) <= 10 остальная часть кода является правильным кодом в соответствии с 4. Conformance, но содержит неопределенное поведение и может содержать неопределенное поведение в зависимости от реализации.

  • request(&devices[0]); и void request(struct message *msg) {...}: Хорошо: request ожидает struct message * и получает адрес первого элемента массива struct message - здесь все в порядке
  • struct request *req = (struct request *)&msg->data;: that is the most interesting part.
    • msg->data is a char array, that is an aggregate. The address of an aggregate is the address of its first byte. Said differently (char *) &msg-> data (address of first byte of the aggregate) is the same as msg->data(array decaying to a pointer to its first element)
    • (struct request *)&msg->data; указатель на char преобразуется в указатель на struct request. В зависимости от реализации ничто не гарантирует, что указатель будет правильно выровнен. Если это не так, то это неопределенное поведение в соответствии с 6.3.2.3 Указатели § 7. Если есть, то мы по-прежнему имеем неопределенное поведение в соответствии с 6.5 Выражения § 6-7, потому что новый указатель будет использоваться для хранения элемента, который не был объявленный тип char[10]. Но это будет принято всеми известными реализациями, потому что внутри они обрабатывают одни и те же массивы символов (явный тип) и выделенную память (без объявленного типа) - просто потому, что им нужно реализовать malloc (см. Можно ли написать соответствующую реализацию malloc на C?)

Остальная часть кода не содержит других проблем. Но следует отметить, что если __attribute__(packed) учитывается реализацией, поле data будет третьим байтом структуры, что дает странное выравнивание. Это может привести к сбоям в реализации, которые требуют строгого выравнивания для определенных типов.


Ссылки из проекта n1256 для C99

  1. Соответствие
    ...
    2 Если нарушается требование "должен" или "не должен", которое выходит за пределы ограничения, поведение не определено...
    3 A программа, правильная во всех других аспектах, работающая с правильными данными, содержащая неопределенное поведение, должна быть правильной программой...

6.3.2.3 Pointers
...
7 A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object.

6.5 Expressions
...
6 The effective type of an object for an access to its stored value is the declared type of the object, if any. If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one. For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.

7 An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

  • тип, совместимый с эффективным типом объекта,
  • уточненная версия типа, совместимая с действующим типом объекта,
  • тип, который является подписанным или беззнаковым типом, соответствующим эффективному типу объекта,
  • тип, который является подписанным или беззнаковым типом, соответствующим уточненной версии эффективного типа объекта,
  • тип агрегата или объединения, который включает один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член подагрегата или содержащего объединения), или
  • тип персонажа.
person Serge Ballesta    schedule 10.04.2017