Как правильно исправить предупреждение «массив нулевого размера в структуре / объединении» (C4200), не нарушая код? - PullRequest
19 голосов
/ 28 июля 2010

Я интегрирую некоторый код в мою библиотеку. Это сложная структура данных, хорошо оптимизированная для скорости, поэтому я стараюсь не слишком ее изменять. Процесс интеграции идет хорошо и фактически почти завершен (он компилируется). Одна вещь все еще беспокоит меня. Я получаю предупреждение 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

Код работает , но это предупреждение вызывает у меня дрожь (особенно в части с copy-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-ed.

Ответы [ 6 ]

15 голосов
/ 28 июля 2010

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

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

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

_TREEDATSTR x=y;

, который все равно не должен работать.

13 голосов
/ 28 июля 2010

Я предполагаю, что вы хотите, чтобы это компилировалось в режиме чистого 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 имеет поле size со значением 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) по сравнению с размером объектаимеет отступ в конце.

3 голосов
/ 28 июля 2010

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

2 голосов
/ 28 июля 2010

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

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

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

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

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

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

Подводя итог:

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

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

1 голос
/ 28 июля 2010

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

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

0 голосов
/ 14 мая 2015

Хотя я понимаю, что это старая ветка, я бы хотел дать свое чистое решение c ++ 11 для вопроса ОП.Идея состоит в том, чтобы обернуть объект, который должен быть выделен, добавив в заполнение, чтобы выровнять объекты в массиве до степени 2 адресов, следующим образом:

template<typename T, std::size_t ObjectPaddingSize>
struct PaddedType : private T { private: char padding [ ObjectPaddingSize ]; };

template<typename T> // No padding.
struct PaddedType<T, 0> : private T { };

template<typename T>
struct PaddedT : private PaddedType<T, NextPowerOfTwo<sizeof ( T )>::value - sizeof ( T )> { };

Размер заполнения объектов можно рассчитать при компиляции-time со следующим классом (возвращает L, если L - степень 2, в противном случае следующая степень 2 gt L):

template<std::size_t L>
class NextPowerOfTwo {

    template <std::size_t M, std::size_t N>
    struct NextPowerOfTwo1 {

        enum { value = NextPowerOfTwo1<N, N & ( N - 1 )>::value };
    };

    template <std::size_t M>
    struct NextPowerOfTwo1<M, 0> {

        enum { value = M << 1 };
    };

    // Determine whether S is a power of 2, if not dispatch.

    template <std::size_t M, std::size_t N>
    struct NextPowerOfTwo2 {

        enum { value = NextPowerOfTwo1<M, M>::value };
    };

    template <std::size_t M>
    struct NextPowerOfTwo2<M, 0> {

        enum { value = M };
    };

public:

    enum { value = NextPowerOfTwo2<L, L & ( L - 1 )>::value };
};
...