Как правильно исправить «нулевой размер массива в struct / union» предупреждение (C4200) без нарушения кода?

18

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

    
задан PeterK 28.07.2010 в 09:46
источник
  • Вы используете std :: vector, если хотите динамический массив. –  GManNickG 28.07.2010 в 09:47
  • Это странная упаковка; у вас будет неправильный DWORD. –  dreamlax 28.07.2010 в 09:53
  • «Подобный вопрос» на самом деле касается стандартной конструкции, тогда как ваш является нестандартным расширением. –  MSalters 28.07.2010 в 09:54
  • У вас есть еще одна несвязанная проблема - создание имен, таких как _TREEDATSTR, которые начинаются с подчеркивания, а заглавная буква является незаконной в коде пользователя. –  Jul 28 '10 at 8:00 28.07.2010 в 10:00
  • @PeterK: директивы #pragma удаляют любые дополнения, которые в противном случае вставлял бы компилятор (обычно для оптимального доступа или требуемого выравнивания для целевой архитектуры). –  dreamlax 28.07.2010 в 10:03

6 ответов

13

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

    
ответ дан David Rodríguez - dribeas 28.07.2010 в 10:03
  • Внимание: если вы определяете назначение копии, тогда runtime_array больше не будет квалифицироваться как POD. –  Nordic Mainframe 28.07.2010 в 10:41
  • @Luther Blissett: верно, но был ли это POD в первый раз? Я не говорю о компиляторе, а о семантике. Для компилятора это похоже на POD, но многие операции, которые могут выполняться на POD, не могут выполняться в этом объекте, например, побитовое копирование при назначении копии. –  David Rodríguez - dribeas 28.07.2010 в 10:47
  • Я полностью с вами в этом. C ++ иногда слишком строг непонятными способами. В любом случае, если я получаю это правильно, то C ++ 0x исправляет это и разрешает POD с тривиальным ctors / dtor / assigment, который сделает ваш код определенным. –  Nordic Mainframe 28.07.2010 в 12:37
  • Я не мог получить предупреждение «нулевой размер», чтобы уйти в MSVC 2012, без явного отказа от предупреждения 4200. Объявление явных закрытых копий-конструкторов и копирование не помогло. –  Mark Lakata 09.06.2014 в 22:07
  • @MarkLakata: Uhm ... да, перечитывая ответ, я заметил, что забыл упомянуть, что, хотя размер массива 0 равен легальному в C, он не находится в C ++ 8.3.4 / 1 Если константа- выражение (5.19) присутствует, оно должно быть интегральным постоянным выражением и его значение должно быть больше нуля. Вам нужно будет использовать 1 вместо 0, некоторые из ваших объектов могут заплатить немного больше за память, но, надеюсь, не слишком много (вероятно, слово) –  David Rodríguez - dribeas 12.06.2014 в 14:32
Показать остальные комментарии
15

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

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

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

_TREEDATSTR x=y;

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

    
ответ дан Nordic Mainframe 28.07.2010 в 09:55
  • Скорее всего, в любом случае не нужен конструктор копирования или оператор присваивания копиям, «структура динамического размера в стиле С», подобная этой, обычно копируется с memcpy и т. д. –  dreamlax 28.07.2010 в 10:02
3

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

    
ответ дан dreamlax 28.07.2010 в 09:57
  • Чтобы быть точным, согласно стандарту, только массив, не выделенный в куче, не может иметь нулевые элементы: int NotStandard [0]; int * Standard = new int [0]; –  Francesco 28.07.2010 в 10:28
  • «как стандарты C ++, так и C указывают, что массив не может содержать 0 элементов» - C99. См. En.wikipedia.org/wiki/Flexible_array_member. Это просто C ++, который здесь излишне строгий, хотя gcc / clang / msvc действительно поддерживают его. –  Dwayne Robinson 28.09.2017 в 02:43
2

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

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

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

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

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

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

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

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

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

    
ответ дан 6502 28.07.2010 в 10:32
  • Еще одна причина, по которой люди это делают, - это местность. Кэш-память и промахи кэшей в наши дни сильно влияют на производительность. Дополнительные направления, связанные с недавно выделенными областями кучи (std :: vector), оказывают негативное влияние на местность. –  BitTickler 21.03.2016 в 07:40
1

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

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

    
ответ дан Brian Hooper 28.07.2010 в 10:19
0

Хотя я понимаю, что это старый поток, я хотел бы дать свое чистое решение c ++ 11 для вопроса OP. Идея состоит в том, чтобы обернуть выделенный объект, добавив в дополнение к выравниванию объектов в массиве с мощностью 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 )> { };

Размер заполнения объектов можно вычислить во время компиляции со следующим классом (возвращает 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 };
};
    
ответ дан degski 14.05.2015 в 05:34