Правильно справиться с проблемами выравнивания байтов — между 16-битной системой и 32-битным рабочим столом через UDP

Приложение, над которым я работаю, получает структуры в стиле C от системы встраивания, код которой был сгенерирован для 16-битного процессора. Приложение, которое говорит со встроенной системой, создается с помощью 32-битного компилятора gcc или 32-битного компилятора MSVC c ++. Связь между приложением и встроенной системой происходит через UDP-пакеты через Ethernet или модем.

Полезная нагрузка в пакетах UDP состоит из различных структур стиля C. На стороне приложения стиль C ++ reinterpret_cast способен принимать массив байтов без знака и приводить его в соответствующую структуру.

Тем не менее, я сталкиваюсь с проблемами с reinterpret_cast когда структура содержит перечисляемые значения. 16-битный компилятор Watcom будет обрабатывать перечисляемые значения как тип uint8_t. Однако на стороне приложения перечисляемые значения обрабатываются как 32-битные значения. Когда я получаю пакет с перечисляемыми значениями в нем, данные искажаются, потому что размер структуры на стороне приложения больше структуры на встроенной стороне.

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

То, что я ищу, — это решение, которое позволит мне использовать простую операцию приведения без вмешательства в определение структуры в исходном коде на стороне приложения. Таким образом, я могу использовать структуру как есть на верхних уровнях моего приложения.

1

Решение

Как уже отмечалось, правильно заниматься проблемой правильной сериализации и десериализации.

Но это не значит, что мы не можем попробовать некоторые хаки.

Опция 1:
Если ваш конкретный компилятор поддерживает упаковку enum (в моем случае gcc 4.7 в windows), это может сработать:

typedef enum { VALUE_1 = 1, VALUE_2, VALUE_3 }__attribute__ ((__packed__)) TheRealEnum;

Вариант 2:

Если ваш конкретный компилятор поддерживает размеры классов < 4 байта, вы можете использовать HackedEnum класс, который использует перегрузку оператора для преобразования (обратите внимание на gcc атрибут ты можешь этого не хотеть)

class HackedEnum
{
private:
uint8_t evalue;
public:
void operator=(const TheRealEnum v) { evalue = v; };
operator TheRealEnum() { return (TheRealEnum)evalue; };
}__attribute__((packed));

Вы бы заменили TheRealEnum в ваших структурах для HackedEnum, но вы все еще продолжаете использовать его как TheRealEnum.

Полный пример, чтобы увидеть, как это работает:

#include <iostream>
#include <stddef.h>

using namespace std;

#pragma pack(push, 1)

typedef enum { VALUE_1 = 1, VALUE_2, VALUE_3 } TheRealEnum;

typedef struct
{
uint16_t v1;
uint8_t enumValue;
uint16_t v2;
}__attribute__((packed)) ShortStruct;

typedef struct
{
uint16_t v1;
TheRealEnum enumValue;
uint16_t v2;
}__attribute__((packed)) LongStruct;

class HackedEnum
{
private:
uint8_t evalue;
public:
void operator=(const TheRealEnum v) { evalue = v; };
operator TheRealEnum() { return (TheRealEnum)evalue; };
}__attribute__((packed));

typedef struct
{
uint16_t v1;
HackedEnum enumValue;
uint16_t v2;
}__attribute__((packed)) HackedStruct;

#pragma pop()

int main(int argc, char **argv)
{
cout << "Sizes: " << endl
<< "TheRealEnum: " << sizeof(TheRealEnum) << endl
<< "ShortStruct: " << sizeof(ShortStruct) << endl
<< "LongStruct: " << sizeof(LongStruct) << endl
<< "HackedStruct: " << sizeof(HackedStruct) << endl;

ShortStruct ss;
cout << "address of ss: " << &ss <<  " size " << sizeof(ss) <<endl
<< "address of ss.v1: " << (void*)&ss.v1 << endl
<< "address of ss.ev: " << (void*)&ss.enumValue << endl
<< "address of ss.v2: " << (void*)&ss.v2 << endl;

LongStruct ls;
cout << "address of ls: " << &ls <<  " size " << sizeof(ls) <<endl
<< "address of ls.v1: " << (void*)&ls.v1 << endl
<< "address of ls.ev: " << (void*)&ls.enumValue << endl
<< "address of ls.v2: " << (void*)&ls.v2 << endl;

HackedStruct hs;
cout << "address of hs: " << &hs <<  " size " << sizeof(hs) <<endl
<< "address of hs.v1: " << (void*)&hs.v1 << endl
<< "address of hs.ev: " << (void*)&hs.enumValue << endl
<< "address of hs.v2: " << (void*)&hs.v2 << endl;uint8_t buffer[512] = {0};

ShortStruct * short_ptr = (ShortStruct*)buffer;
LongStruct * long_ptr = (LongStruct*)buffer;
HackedStruct * hacked_ptr = (HackedStruct*)buffer;

short_ptr->v1 = 1;
short_ptr->enumValue = VALUE_2;
short_ptr->v2 = 3;

cout << "Values of short: " << endl
<< "v1 = " << short_ptr->v1 << endl
<< "ev = " << (int)short_ptr->enumValue << endl
<< "v2 = " << short_ptr->v2 << endl;

cout << "Values of long: " << endl
<< "v1 = " << long_ptr->v1 << endl
<< "ev = " << long_ptr->enumValue << endl
<< "v2 = " << long_ptr->v2 << endl;

cout << "Values of hacked: " << endl
<< "v1 = " << hacked_ptr->v1 << endl
<< "ev = " << hacked_ptr->enumValue << endl
<< "v2 = " << hacked_ptr->v2 << endl;HackedStruct hs1, hs2;

// hs1.enumValue = 1; // error, the value is not the wanted enum

hs1.enumValue = VALUE_1;
int a = hs1.enumValue;
TheRealEnum b = hs1.enumValue;
hs2.enumValue = hs1.enumValue;

return 0;
}

Вывод в моей конкретной системе:

Sizes:
TheRealEnum: 4
ShortStruct: 5
LongStruct: 8
HackedStruct: 5
address of ss: 0x22ff17 size 5
address of ss.v1: 0x22ff17
address of ss.ev: 0x22ff19
address of ss.v2: 0x22ff1a
address of ls: 0x22ff0f size 8
address of ls.v1: 0x22ff0f
address of ls.ev: 0x22ff11
address of ls.v2: 0x22ff15
address of hs: 0x22ff0a size 5
address of hs.v1: 0x22ff0a
address of hs.ev: 0x22ff0c
address of hs.v2: 0x22ff0d
Values of short:
v1 = 1
ev = 2
v2 = 3
Values of long:
v1 = 1
ev = 770
v2 = 0
Values of hacked:
v1 = 1
ev = 2
v2 = 3
2

Другие решения

На стороне приложения reinterpret_cast в стиле C ++ способен принимать массив байтов без знака и преобразовывать его в соответствующую структуру.

Расположение структур не обязательно должно быть одинаковым для разных реализаций. Использование reinterpret_cast таким способом не подходит.

16-битный компилятор Watcom будет обрабатывать перечисляемые значения как тип uint8_t. Однако на стороне приложения перечисляемые значения обрабатываются как 32-битные значения.

Базовый тип перечисления выбирается реализацией и выбирается способом, определяемым реализацией.

Это лишь одно из многих потенциальных различий между реализациями, которые могут вызвать проблемы с вашим reinterpret_cast. Существуют также реальные проблемы с выравниванием, если вы не будете осторожны, когда данные в полученном буфере не будут соответствующим образом выровнены для типов (например, целое число, которое требует четырехбайтового выравнивания, заканчивается на один байт), что может привести к сбоям или плохому результату. спектакль. Заполнение может быть различным для разных платформ, фундаментальные типы могут иметь разные размеры, порядковые номера могут отличаться и т. Д.

То, что я ищу, — это решение, которое позволит мне использовать простую операцию приведения без вмешательства в определение структуры в исходном коде на стороне приложения. Таким образом, я могу использовать структуру как есть на верхних уровнях моего приложения.

В C ++ 11 введен новый синтаксис enum, который позволяет указывать базовый тип. Или вы можете заменить свои перечисления целочисленными типами вместе с набором предопределенных констант с объявленными вручную значениями. Это только устраняет проблему, о которой вы спрашиваете, а не любую другую, которая у вас есть.

То, что вы действительно должны сделать, это правильная сериализация и десериализация.

1

Поместите ваш перечислимый тип в объединение с 32-битным числом:

union
{
Enumerated val;
uint32_t valAsUint32;
};

Это сделало бы встроенную сторону расширенной до 32-битной. Должен работать до тех пор, пока обе платформы имеют младший порядок и структуры изначально заполнены нулями. Это изменило бы формат провода, хотя.

0

Если под «простой операцией приведения» вы подразумеваете что-то, что выражено в исходном коде, а не что-то, что обязательно должно иметь нулевое копирование, тогда вы можете написать две версии структуры — одну с перечислениями, одну с uint8_ts и конструктор для одного от другого, который копирует это элемент за элементом, чтобы упаковать это. Затем вы можете использовать обычное приведение типов в остальной части кода. Поскольку размеры данных принципиально отличаются (если вы не используете функции C ++ 11, упомянутые в другом ответе), вы не сможете сделать это, не копируя вещи для их перепаковки.

Однако, если вы не возражаете против небольших изменений в определении структуры на стороне приложения, есть пара опций, которые не связаны с обработкой голых значений uint8_t. Вы можете использовать ответ aaronps для класса, который имеет размер uint8_t (при условии, что это возможно с вашим компилятором) и неявно преобразует в и из перечисления. С другой стороны, вы можете сохранить значения как uint8_ts и написать несколько методов доступа для значений enum, которые принимают данные uint8_t в структуре и преобразуют их в перечисление перед его возвратом.

0
По вопросам рекламы [email protected]