2014-11-10 2 views
3

В моей повседневной работе я столкнулся с большим количеством кодов C, напоминающих следующий шаблон. Я беспокоюсь, безопасен ли этот шаблон.Безопасно ли использовать индекс вне диапазона с меньшим массивом, который вытесняется из достаточно большого массива?

typedef struct 
{ 
    unsigned char someField : 4; 
    unsigned char someOtherField : 4; 
    unsigned char body[1]; 
} __attribute__((__packed__, aligned(1))) SomeStruct; 

int main() 
{ 
    unsigned char pack[16] = {}; 
    SomeStruct* structPack = (SomeStruct*)pack; 

    structPack->someField = 0xC; 
    structPack->body[4] = 0x5; 

    return 0; 
} 

Что заставляет меня беспокоиться, что программа использует structPack->body[4], который по-прежнему является частью массива 16 байт, но вне переплете, если мы посмотрим на определение SomeStruct. Таким образом, есть два способа взглянуть на это:

  • Это ссылка на действительную ячейку памяти. Нет опасности.
  • Это внеочередное, поэтому неопределенное поведение.

Итак, мои вопросы:

  1. Согласно стандарту C (более конкретно, C89), эта модель безопасна или неопределенное поведение?
  2. Кроме того, для некоторых конкретных компиляторов (особенно GCC) или платформы это безопасно?
  3. Есть ли лучшие альтернативы?

Обратите внимание, что этот тип кода в основном работает на микроконтроллерах и иногда работает как приложение на рабочем столе Linux.

+0

Я считаю это небезопасным и в лучшем случае вводит в заблуждение. Нет смысла делать это. Если код, написанный в 'structPack-> body [4]', был перемещен в другое место, и он больше не указывал на больший буфер, у вас проблемы. Или, если кто-то решит создать массив SomeStruct, у вас будет плохое время. –

+0

Если тело представляет собой массив размером 1, то почему вы хотите получить доступ к 5-му элементу? Что-то не так с вашей логикой здесь. –

+0

@NeilKirk Это обычно используемый шаблон в C для выделения одного объекта с массивом переменной длины в конце. Обычно объект выделяется через 'malloc'. Но не знаю, строго ли это законно или нет. – Keith

ответ

3

Доступ к объекту через несовместимое значение lvalue является неопределенным поведением. Выравнивание может быть решено с помощью вашего атрибута линии, но с помощью указателя для доступа к объекту по-прежнему нарушает строгие ступенчатости:

unsigned char pack[16] = {}; 
SomeStruct* structPack = (SomeStruct*)pack; 

6.5. P7:

Объект должен иметь свое сохраненное значение доступно только именующее выражение, которое имеет одну из следующих типов:

- тип совместимых с эффективным типом объекта,

- это квалифицированная версия типа, совместимого с эффективным типом объекта,

- тип, который является знаком или без знака типа, соответствующего эффективного типа объекта,

- типа, который является знаком или без знака типа, соответствующим квалифицированной версия эффективного типа объекта,

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

- тип символа.

Где эффективный тип является:

Эффективный тип объекта для доступа к его сохраненным значением является объявленный тип объекта, если таковые имеются.

SomeStruct* не совместим с массивом символов.

Правильный способ выделения SomeStruct - использовать распределители памяти или alloca (который будет выделять стек, если это является проблемой), если функция поддерживается.

Все еще существует проблема члена body, который является массивом размера, и Standard не разрешает доступ к нему за пределы (т. Е. Тело [1]). c99 представила решение, которое является гибким элементом массива:

typedef struct 
{ 
    unsigned char someField : 4; 
    unsigned char someOtherField : 4; 
    unsigned char body[]; //must be last 
}... 

Когда вы установите размер выделить эту структуру, вы добавляете дополнительный размер в зависимости, насколько большой body[] член должен быть.