Заставить две структуры иметь одинаковый размер во время компиляции?

Я определил две структуры данных, которые должны оставаться одного и того же размера, чтобы приложение функционировало должным образом. Структура используется для связи между ПК и 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];
};