Заставить две структуры иметь одинаковый размер во время компиляции?
Я определил две структуры данных, которые должны оставаться одного и того же размера, чтобы приложение функционировало должным образом. Структура используется для связи между ПК и DSP. Код DSP находится на "C", стороне ПК в C++.
например:
struct inbound_data{
int header[5];
float val1;
float val2;
int trailer[3];
};
struct outbound_data{
int header[5];
int reply1;
int reply2;
float dat1;
float dat2;
int filler[1];
}
позже я сделаю что-то вроде:
int tx_block[sizeof(outbound_data)];
int rx_block[sizeof(inbound_data)];
Эти массивы будут передаваться периферийным устройствам связи для передачи и приема между устройствами.
Из-за того, как работает оборудование, важно, чтобы размер двух структур соответствовал, чтобы буферы были одинакового размера. Это достаточно легко обеспечить с должным вниманием, но иногда через цикл проектирования структуры данных становятся модифицированными. Если вы не очень осторожны и осознаете требование о том, чтобы структуры оставались одного и того же размера (и также отражались в коде на стороне ПК), возникает хаос.
Я хотел бы найти способ компиляции, чтобы код не строился, если одна из структур модифицирована так, что она не соответствует размеру другой структуры.
Возможно ли это как-то в "стандартном" C проверять размеры во время компиляции и терпеть неудачу, если они разные? (Я думаю, что мой компилятор по крайней мере C99, может быть, не 11).
Ответы
Ответ 1
Если вы должны использовать C99, то я тоже, как Swordfish, предложит макрос. Способ сделать тот, который может появиться в любом месте и не будет вводить какие-либо объекты для удаления оптимизатора, заключается в том, чтобы помещать недопустимый массив в typedef
. Таким образом, статическое утверждение более общего назначения будет выглядеть так:
#define CONCAT_(A,B) A##B
#define CONCAT(A,B) CONCAT_(A,B)
#define MY_STATIC_ASSERT(p, msg) typedef char CONCAT(dummy__,__LINE__) [(p) ? 1 : -1]
Он предназначен для имитации _Static_assert
. Сообщение передается с надеждой на его диагностику компилятора. Примером его использования является здесь.
Что производит:
main.cpp:4:54: error: size of array 'dummy__13' is negative
#define MY_STATIC_ASSERT(p, msg) typedef char CONCAT(dummy__,__LINE__) [(p) ? 1 : -1]
^~~~~~~
main.cpp:2:22: note: in definition of macro 'CONCAT_'
#define CONCAT_(A,B) A##B
^
main.cpp:4:47: note: in expansion of macro 'CONCAT'
#define MY_STATIC_ASSERT(p, msg) typedef char CONCAT(dummy__,__LINE__) [(p) ? 1 : -1]
^~~~~~
main.cpp:13:1: note: in expansion of macro 'MY_STATIC_ASSERT'
MY_STATIC_ASSERT(sizeof(struct foo) == sizeof(struct baz), "Do not match!");
И весь путь вниз вы можете увидеть статическое утверждение с сообщением.
Как запоздалая мысль, вы можете изменить dummy__
на please_check_line_
и это приведет к более описательной please_check_line_13
.
Ответ 2
В стандарте C11 добавлено новое ключевое слово _Static_assert
. Вы можете использовать его для проверки предиката во время компиляции и создания ошибки, если оно false
:
_Static_assert(sizeof(outbound_data) == sizeof(inbound_data), "sizes must match");
Ответ 3
Использовать две структуры имеют одинаковый размер во время компиляции?
Нет стандартного способа принудительного применения этого в C. Есть только способы защитить его от происходящего, например static_assert
- который предотвращает компиляцию кода багги, но не решает актуальную проблему.
В вашем случае есть несколько проблем:
- Ваша структура использует наивные типы по умолчанию C. Они не переносимы и могут иметь любой размер. Это можно легко устранить путем замены
int
для int32_t
и т.д. - Endianess может сделать код не переносным, независимо от целочисленного типа. Это отдельная проблема, которую я здесь не буду рассматривать, но ее нужно учитывать, особенно для экзотических DSP.
- Любая структура может содержать байты заполнения в любом месте, чтобы удовлетворить требования к выравниванию системы. Корень проблемы состоит в том, что выравнивание работает по-разному в разных системах. Это трудно решить.
Грязное исправление, чтобы избежать заполнения - использовать static_assert
вместе с некоторыми нестандартными решениями, чтобы гарантировать, что структура имеет ожидаемый размер. Например, #pragma pack(1)
или gcc __attribute__ ((__packed__))
и т.д. Они не являются стандартными и не переносимы. Кроме того, пропуская прокладку может быть проблематичным на многих системах, и вы можете получить проблемы с несогласованным доступом - добавление происходит по какой-то причине. Таким образом, это потенциально может создать больше проблем, чем решает.
Поэтому, к сожалению, мы заканчиваем тем, что struct
не подходит для переносимого кода. Особенно для таких вещей, как спецификации протокола данных.
Если вам нужен действительно портативный, надежный код, он оставляет вам только один параметр, а именно использовать необработанный массив данных uint8_t
. Если вам понадобится перевести этот массив в структуры, вам придется написать код сериализации/де-сериализации. Который будет стоить накладные расходы. Но нет другого пути, если вы хотите действительно портативные структуры.
Ответ 4
Для C99 вы можете использовать что-то вроде
#define C_ASSERT(x, y) { int dummy[(x) == (y) ? 1 : -1]; (void*)dummy; }
struct foo {
int f;
};
struct bar {
int b1;
//int b2;
};
int main()
{
C_ASSERT(sizeof(struct foo), sizeof(struct bar));
}
Ответ 5
вы можете добавить отступы для выравнивания размера
struct inbound_data;
struct outbound_data;
struct _inbound_data{
int header[5];
float val1;
float val2;
int trailer[3];
};
struct _outbound_data{
int header[5];
int reply1;
int reply2;
float dat1;
float dat2;
int filler[1];
};
struct inbound_data{
int header[5];
float val1;
float val2;
int trailer[3];
char padding[sizeof(struct _inbound_data) < sizeof(struct _outbound_data) ? sizeof(struct _outbound_data) - sizeof(struct _inbound_data) : 0];
};
struct outbound_data{
int header[5];
int reply1;
int reply2;
float dat1;
float dat2;
int filler[1];
char padding[sizeof(struct _outbound_data) < sizeof(struct _inbound_data) ? sizeof(struct _inbound_data) - sizeof(struct _outbound_data) : 0];
};
Я, конечно, мог бы писать более короткий путь без дублирования структурных элементов, но я сделал это намеренно, чтобы показать эту идею.
struct inbound_data1 __attribute__((packed){
struct _inbound_data id;
char padding[sizeof(struct _inbound_data) < sizeof(struct _outbound_data) ? sizeof(struct _outbound_data) - sizeof(struct _inbound_data) : 0];
};
struct outbound_data1 __attribute__((packed){
struct _outbound_data od;
char padding[sizeof(struct _outbound_data) < sizeof(struct _inbound_data) ? sizeof(struct _inbound_data) - sizeof(struct _outbound_data) : 0];
};