Как правильно зафиксировать “массив нулевого размера в структуре/объединении” предупреждение (C4200), не взламывая код?

Я интегрирую некоторый код в свою библиотеку. Это - сложная структура данных, хорошо оптимизированная для скорости, таким образом, я пытаюсь не изменить его слишком много. Процесс интеграции подходит и на самом деле почти закончен (он компилирует). Одна вещь все еще беспокоит меня. Я получаю C4200, предупреждающий многократно:

warning C4200: nonstandard extension used : zero-sized array in struct/union
Cannot generate copy-ctor or copy-assignment operator when UDT contains a zero-sized array

Работы кода, но это предупреждение дают мне сползания (особенно часть с копией-ctor). Предупреждение появляется из-за структур, объявленных как это:

#pragma pack( push )
#pragma pack( 1 )
// String
struct MY_TREEDATSTR
{
    BYTE btLen;
    DWORD dwModOff;
    BYTE btPat[0];
};

typedef MY_TREEDATSTR TREEDATSTR;
typedef MY_TREEDATSTR *PTREEDATSTR;

#pragma pack( pop )

Отметьте btPat[0]. Есть ли путь, как к легко и правильно избавляются от этого предупреждения, не взламывая код и/или имея необходимость измениться слишком много в нем. Заметьте #pragmaих любое значение согласно этому предупреждению имеют? И почему структура объявляется этим путем так или иначе? (Я имею в виду btPat вещь, не #pragmaони я понимаю).

Примечание: я видел этот подобный вопрос, но он действительно не помог мне.

Обновление: как я сказал, код работает и дает корректные результаты. Так конструктор копии или оператор присваивания по-видимому действительно не нужно. И поскольку я смотрю на код, ни одна из структур не получает memcpy-редактора.

19
задан Community 23 May 2017 в 11:45
поделиться

5 ответов

Я предполагаю, что вы хотите, чтобы это было скомпилировано в чистом режиме C ++, и что вы не хотите просто компилировать некоторые файлы на C, а некоторые - на C ++ и более поздние ссылки.

Предупреждение говорит вам, что сгенерированный компилятором конструктор копирования и присваивание, скорее всего, будут неправильными с вашей структурой. Использование массивов нулевого размера в конце структуры обычно в C является способом получения массива, который определяется во время выполнения, но является незаконным в C ++, но вы можете получить аналогичное поведение с размером 1:

struct runtime_array {
   int size;
   char data[1];
};
runtime_array* create( int size ) {
   runtime_array *a = malloc( sizeof(runtime_array) + size ); // [*]
   a->size = size;
   return a;
}
int main() {
   runtime_array *a = create( 10 );
   for ( int i = 0; i < a->size; ++i ) {
      a->data[i] = 0;
   }
   free(a);
}

Этот тип структур предназначен для динамического размещения - или с помощью уловок с динамическим распределением стека - и обычно не копируется, но если вы попытаетесь, вы получите странные результаты:

int main() {
   runtime_array *a = create(10);
   runtime_array b = *a;          // ouch!!
   free(a);
}

В этом примере сгенерированный компилятором конструктор копии будет выделять ровно sizeof (runtime_array) байт в стеке, а затем скопируйте первую часть массива в b . Проблема в том, что b имеет поле размера , в котором указано 10, но вообще не имеет памяти ни для одного элемента.

Если вы все еще хотите иметь возможность скомпилировать это на C, вы должны разрешить предупреждение, закрыв глаза: молчите это конкретное предупреждение.Если вам нужна только совместимость с C ++, вы можете вручную отключить создание и присваивание копий:

struct runtime_array {
   int size;
   char data[1];
private:
   runtime_array( runtime_array const & );            // undefined
   runtime_array& operator=( runtime_array const & ); // undefined
};

Объявив конструктор копирования и оператор присваивания, компилятор не сгенерирует его для вас (и не будет жаловаться на это, не зная как). Имея два частных, вы получите ошибки времени компиляции, если по ошибке попытаетесь использовать их в коде. Поскольку они никогда не вызываются, их можно оставить неопределенными - это также используется, чтобы не вызывать его из другого метода класса, но я предполагаю, что других методов нет.

Поскольку вы выполняете рефакторинг до C ++, я бы также сделал конструктор по умолчанию закрытым и предоставил статический общедоступный встроенный метод, который позаботится о правильном распределении содержимого. Если вы также сделаете деструктор закрытым, вы можете убедиться, что пользовательский код не пытается вызвать delete для ваших объектов:

struct runtime_array {
   int size;
   char data[1];
   static runtime_array* create( int size ) {
      runtime_array* tmp = (runtime_array*)malloc(sizeof(runtime_array)+size);
      tmp->size = size;
      return tmp;
   }
   static void release( runtime_array * a ) {
      free(a);
   }
private:
   runtime_array() {}
   ~runtime_array() {}
   runtime_array( runtime_array const & );            // undefined
   runtime_array& operator=( runtime_array const & ); // undefined
};

Это гарантирует, что пользовательский код по ошибке не создаст ваши объекты в стеке или будет ли он смешивать вызовы malloc / free с вызовами new / delete , поскольку вы управляете созданием и уничтожением ваших объектов. Ни одно из этих изменений не влияет на структуру памяти ваших объектов.

[*] Вычисление размера здесь немного неверно и будет превышено, вероятно, на столько, сколько на sizeof (int) , поскольку размер объекта имеет отступы в конце.

13
ответ дан 30 November 2019 в 03:48
поделиться

Попробуйте изменить его на btPat[1] вместо этого. Я думаю, что стандарты C++ и C диктуют, что массив не может иметь 0 элементов. Это может вызвать проблемы для любого кода, который полагается на размер самой структуры _TREEDATSTR, но обычно такие структуры типизируются из буферов, где (в данном случае) первый байт буфера определяет, сколько байт на самом деле находится в btPat. Такой подход опирается на тот факт, что в массивах языка Си нет проверки границ.

3
ответ дан 30 November 2019 в 03:48
поделиться

Если он жалуется на функции конструктора копирования и оператора присваивания, не могли бы вы предоставить свои собственные? Если они вам не нужны, объявите их частными.

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

1
ответ дан 30 November 2019 в 03:48
поделиться

Основная идея этого (анти) шаблона C состоит в том, чтобы получить для элементов _TREEDATSTR необходимую дополнительную память; другими словами, распределение будет выполняться с помощью malloc (sizeof (_TREEDATSTR) + len).

Pragma pack используется, чтобы попросить компилятор не оставлять пустых пробелов между полями (обычно компиляторы иногда оставляют некоторые неиспользуемые байты между полями структур, чтобы гарантировать выравнивание, потому что во многих современных процессорах это значительно улучшает скорость).

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

Думаю, я бы поставил двойное слово первым в структуре, и для этого, вероятно, не потребовался бы пакет прагмы; также способ заглушить предупреждение - выделить массив из одного элемента и выполнить выделение с использованием (len-1) дополнительных байтов.

В C ++ все это довольно опасно, потому что вы в основном обманываете компилятор, думая, что размер объекта меньше, чем он есть на самом деле, и, учитывая, что C ++ является языком логики копирования, это означает, что вы просите неприятностей (например, для функций создания копий и присваивания, которые будут действовать только на первую часть объекта).Для повседневного использования, безусловно, НАМНОГО лучше использовать, например, std :: vector, но это, конечно, будет иметь более высокую цену (двойное косвенное обращение, больше памяти для каждого экземпляра _TREEDATSTR).

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

Подведем итог:

  1. Использование массива с нулевым элементом в конце массива - это уловка, используемая для создания объектов переменного размера. Распределение выполняется путем запроса sizeof (структура) + n * sizeof (array_element) байтов.
  2. Пакет Pragma используется, чтобы сообщить компилятору, чтобы он не добавлял лишние байты заполнения между полями структуры. Это необходимо, когда требуется точный контроль над компоновкой памяти (например, потому что к этим объектам обращается рукописная сборка)
  3. Не делайте этого в C ++, если вам это действительно не нужно, и вы знаете, что вы do

Невозможно «правильно» отключить предупреждение, потому что код хочет играть грязно (а компиляторы C ++ не любят, когда их вводят в заблуждение относительно размера объекта). Если вы используете этот объект внутри других объектов или в качестве основы для других объектов, или передаете его по значению, то, что бы ни случилось, вы просили об этом.

2
ответ дан 30 November 2019 в 03:48
поделиться

Если это компилятор MSVC (о чем мне говорит предупреждающее сообщение), то вы можете отключить это предупреждение, используя предупреждение #pragma, т.е.:

#pragma warning( push )
#pragma warning( disable : 4200 )
struct _TREEDATSTR
{
    BYTE btLen;
    DWORD dwModOff;
    BYTE btPat[0];
};
#pragma warning( pop )

BTW, сообщение о copy-constructor не жуткое, но хорошая вещь, потому что это означает, что вы не можете копировать экземпляры _TREEDATSTR без неизвестных байтов в btPat: Компилятор понятия не имеет, насколько велика _TREEDATSTR на самом деле (из-за массива 0-size) и поэтому отказывается генерировать конструктор копирования. Это означает, что вы не можете этого сделать:

_TREEDATSTR x=y;

, что в любом случае не должно работать.

15
ответ дан 30 November 2019 в 03:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: