Как безопасно выполнять штампование типа во встроенной системе - PullRequest
6 голосов
/ 26 января 2012

В настоящее время наша команда использует некоторый перенесенный код из старой архитектуры в новый продукт на платформе ARM Cortex M3 с использованием настроенной версии GCC 4.5.1.Мы читаем данные из канала связи и пытаемся преобразовать необработанный байтовый массив в структуру для точного анализа данных.После наведения указателя на структуру и разыменования мы получаем предупреждение: «разыменование указателя типа, нарушенного правилами, будет нарушать правила строгого наложения имен».

После некоторых исследований я понял, что, поскольку массив char имеетнет правил выравнивания, и структура должна быть выровнена по словам, приведение указателей приводит к неопределенному поведению (плохая вещь).Мне интересно, есть ли лучший способ сделать то, что мы пытаемся.

Я знаю, что мы можем явно выровнять слова в массиве символов с помощью атрибута GCC ((выровненный (4)))».Я полагаю, что это сделает наш код "более безопасным", но предупреждения все еще будут загромождать наши сборки, и я не хочу отключать предупреждения в случае повторного возникновения этой ситуации.То, что мы хотим, - это способ безопасно делать то, что мы пытаемся, который будет информировать нас, если мы попытаемся сделать что-то небезопасное в другом месте позже.Поскольку это встроенная система, использование ОЗУ и флэш-памяти в некоторой степени важно.

Переносимость (компилятор и архитектура) не представляет большой проблемы, это касается только одного продукта.Однако, если существует переносимое решение, оно было бы предпочтительным.

Вот (очень упрощенный) пример того, что мы в настоящее время делаем:

#define MESSAGE_TYPE_A 0
#define MESSAGE_TYPE_B 1

typedef struct MessageA __attribute__((__packed__))
{
    unsigned char  messageType;
    unsigned short data1;
    unsigned int   data2;
}

typedef struct MessageB __attribute__((__packed__))
{
    unsigned char  messageType;
    unsigned char  data3;
    unsigned char  data4;
}


// This gets filled by the comm system, assume from a UART interrupt or similar
unsigned char data[100];


// Assume this gets called once we receive a full message
void ProcessMessage()
{
    MessageA* messageA;
    unsigned char messageType = data[0];

    if (messageType == MESSAGE_TYPE_A)
    {
        // Cast data to struct and attempt to read
        messageA = (MessageA*)data; // Not safe since data may not be word aligned
                                    // This may cause undefined behavior

        if (messageA->data1 == 4) // warning would be here, when we use the data at the pointer
        {
            // Perform some action...
        }
    }
    // ...
    // process different types of messages
}

Ответы [ 6 ]

6 голосов
/ 26 января 2012

Как уже указывалось, наведение указателей на это - хитрая практика.

Решение: используйте штуцер

struct message {
  unsigned char messageType;
  union {
    struct {
      int data1;
      short data2;
    } A;
    struct {
      char data1[5];
      int data2;
    } B;
  } data;
};

void func (...) {
  struct message msg;
  getMessage (&msg);

  switch (msg.messageType) {
    case TYPEA:
      doStuff (msg.data.A.data1);
      break;
    case TYPEB:
      doOtherStuff (msg.data.B.data1);
      break;
  }
}

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

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

4 голосов
/ 26 января 2012

Проникновение типов через приведение типов, отличных от char *, или указатель на подписанный / беззнаковый вариант char не является строго соответствующим, поскольку он нарушает правила псевдонимов C (и иногда правила выравнивания, если не проявляется осторожность).

Тем не менее, gcc разрешает наказание типов через объединяемые типы.Manpage of gcc явно документирует это:

Практика чтения от члена профсоюза, отличного от того, к которому недавно был написан (так называемый "наказание типа"), распространена.Даже с параметром -fstrict-aliasing допускается перетаскивание типов при условии, что доступ к памяти осуществляется через тип объединения.

Отключение оптимизаций, связанных с правилами наложения имен с помощью gcc (и, таким образом, позволяющих программенарушать правила псевдонимов C), программу можно скомпилировать с помощью: -fno-strict-aliasing.Обратите внимание, что с включенной этой опцией программа больше не является строго соответствующей, но вы сказали, что переносимость не имеет значения.Для информации, ядро ​​Linux скомпилировано с этой опцией.

2 голосов
/ 26 января 2012

Прекратить использование упакованных структур и memcpy отдельных полей в переменные правильного размера и типа. Это безопасный, портативный, чистый способ сделать то, что вы пытаетесь достичь. Если вам повезет, gcc оптимизирует крошечный фиксированный размер memcpy в несколько простых инструкций по загрузке и сохранению.

2 голосов
/ 26 января 2012

GCC имеет флаг -fno-strict-aliasing, который отключит оптимизацию на основе строгих псевдонимов и сделает ваш код безопасным.

Если вы действительно ищете способ «исправить» его, вы должны переосмыслитькак работает ваш кодВы не можете просто наложить структуру так, как пытаетесь, поэтому вам нужно сделать что-то вроде этого:

MessageA messageA;
messageA.messageType = data[0];
// Watch out - endianness and `sizeof(short)` dependent!
messageA.data1 = (data[1] << 8) + data[2];
// Watch out - endianness and `sizeof(int)` dependent!
messageA.data2 = (data[3] << 24) + (data[4] << 16)
               + (data[5] <<  8) + data[6];

Этот метод позволит вам избежать упаковки вашей структуры, что также может улучшить ее характеристики производительностив другом месте в вашем коде.Поочередно:

MessageA messageA;
memcpy(&messageA, data, sizeof messageA);

Сделаем это с вашими упакованными структурами.Вы должны выполнить обратные операции, чтобы при необходимости перевести структуры обратно в плоский буфер.

1 голос
/ 26 января 2012

Cortex M3 прекрасно справляется с выравниванием доступа.Я сделал это в аналогичных системах обработки пакетов с M3.Вам не нужно ничего делать, вы можете просто использовать флаг -fno-strict-aliasing, чтобы избавиться от предупреждения.

0 голосов
/ 26 января 2012

Для доступа без выравнивания посмотрите макросы linux get_unaligned / put_unaligned.

...