Подтвердить что ты не робот

Как правильно исправить "нулевой размер массива в struct/union" предупреждение (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-ed.

4b9b3361

Ответ 1

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

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

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

Поскольку вы рефакторинг на С++, я бы также сделал конструктор по умолчанию закрытым и предоставил статический общедоступный встроенный метод, который позаботится о правильном размещении содержимого. Если вы также сделаете деструктор частным, вы можете убедиться, что код пользователя не пытается вызвать 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), так как размер объекта имеет дополнение в конце.

Ответ 2

Если это компилятор 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;

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

Ответ 3

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

Ответ 4

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

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

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

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

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

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

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

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

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

Ответ 5

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

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

Ответ 6

Хотя я понимаю, что это старый поток, я хотел бы дать свое чистое решение С++ 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 };
};