如何在不破坏代码的情况下正确修复"struct/union中的零大小数组"警告(C4200)?

Pet*_*erK 19 c++ struct warnings memory-management

我正在将一些代码集成到我的库中.这是一个针对速度进行了优化的复杂数据结构,因此我不打算过多地修改它.整合过程顺利,实际上几乎完成(它编译).有一件事还在困扰着我.我多次收到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
Run Code Online (Sandbox Code Playgroud)

代码有效,但是这个警告给了我毛骨悚然(特别是带有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 )
Run Code Online (Sandbox Code Playgroud)

Note the btPat[0]. Is there a way how to easily and correctly get rid of this warning without breaking the code and/or having to change too much in it. Notice the #pragma's, have their any significance according to this warning? And why is the structure declared this way anyway? (I mean the btPat thing, not the #pragma's, those i understand).

Note: i saw this similar question, but it really didn't help me.

Update: as I said, the code works and gives correct results. So a copy-constructor or assignment operator is apparently really not needed. And as i look at the code, none of the structures get memcpy-ed.

Nor*_*ame 15

如果这是一个MSVC编译器(警告消息告诉我),那么您可以使用#pragma warning禁用此警告,即:

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

顺便说一句,关于复制构造函数的消息并不令人毛骨悚然,但这是一件好事,因为它意味着你不能在没有btPat中的未知字节的情况下复制_TREEDATSTR的实例:编译器不知道_TREEDATSTR究竟有多大(因为0大小的数组),因此拒绝生成复制构造函数.这意味着,你不能这样做:

_TREEDATSTR x=y;
Run Code Online (Sandbox Code Playgroud)

反正不应该工作.

  • 有可能无论如何都不需要复制构造函数或复制赋值运算符,像这样的"C风格动态大小的结构"通常用`memcpy`等复制. (4认同)

Dav*_*eas 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);
}
Run Code Online (Sandbox Code Playgroud)

这种类型的结构意味着动态分配 - 或者使用动态堆栈分配技巧 - 并且通常不会被复制,但如果你尝试过,你会得到奇怪的结果:

int main() {
   runtime_array *a = create(10);
   runtime_array b = *a;          // ouch!!
   free(a);
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,编译器生成的复制构造函数将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
};
Run Code Online (Sandbox Code Playgroud)

通过声明复制构造函数和赋值运算符,编译器不会为您生成一个(并且不会抱怨它不知道如何).如果错误地尝试在代码中使用它,那么通过使两个私有您将得到编译时错误.因为它们从未被调用过,所以它们可以保持未定义 - 这也用于避免从类的不同方法中调用它,但我认为没有其他方法.

由于您正在重构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
};
Run Code Online (Sandbox Code Playgroud)

这将确保用户代码不会错误地在堆栈中创建对象,也不会将调用malloc/free与调用混合new/delete,因为您管理对象的创建和销毁.这些更改都不会影响对象的内存布局.

[*]这里的大小计算有点偏,并且会分配,可能sizeof(int)与对象的大小在末尾有填充一样多.