C++ 如何在不破坏代码的情况下正确修复“结构/联合中的零大小数组”警告(C4200)?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3350852/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to correctly fix "zero-sized array in struct/union" warning (C4200) without breaking the code?
提问by PeterK
I'm integrating some code into my library. It is a complex data structure well optimized for speed, so i'm trying not to modify it too much. The integration process goes well and actually is almost finished (it compiles). One thing is still bothering me. I'm getting the C4200 warning multiple times:
我正在将一些代码集成到我的库中。这是一个复杂的数据结构,针对速度进行了很好的优化,因此我尽量不要对其进行过多修改。集成过程进展顺利,实际上几乎完成(编译)。一件事仍然困扰着我。我多次收到 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
The code worksbut this warning gives me creeps (especially the part with copy-ctor). THe warning appears because of structures declared like this:
代码有效,但这个警告让我毛骨悚然(尤其是带有 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 )
Note the btPat[0]
. Is there a way how to easily and correctlyget 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).
注意btPat[0]
. 有没有办法在不破坏代码和/或不必对其进行太多更改的情况下轻松正确地摆脱此警告。注意#pragma
's,根据这个警告,它们有什么意义吗?为什么结构以这种方式声明?(我的意思是btPat
事情,而不是#pragma
我理解的那些)。
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.
更新:正如我所说,代码有效并给出了正确的结果。所以显然真的不需要复制构造函数或赋值运算符。当我查看代码时,没有一个结构得到了 memcpy-ed。
采纳答案by David Rodríguez - dribeas
I'll assume that you do want this to be compiled in pure C++ mode, and that you don't want just to compile some files in C and some in C++ and later link.
我假设您确实希望以纯 C++ 模式编译它,并且您不想只用 C 编译一些文件,而用 C++ 编译一些文件,然后链接。
The warning is telling you that the compiler generated copy constructor and assignment will most probably be wrong with your structure. Using zero-sized arrays at the end of a struct is usually a way, in C, of having an array that is decided at runtime, but is illegal in C++, but you can get similar behavior with a size of 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);
}
This type of structures are meant to be allocated dynamically --or with dynamic stack allocation trickery--, and are not usually copied, but if you tried you would get weird results:
这种类型的结构旨在动态分配——或使用动态堆栈分配技巧——,通常不会被复制,但如果你尝试过,你会得到奇怪的结果:
int main() {
runtime_array *a = create(10);
runtime_array b = *a; // ouch!!
free(a);
}
In this example the compiler generated copy constructor would allocate exactly sizeof(runtime_array)
bytes in the stack and then copy the first part of the array into b
. The problem is that b
has a size
field saying 10 but has no memory for any element at all.
在此示例中,编译器生成的复制构造函数将sizeof(runtime_array)
在堆栈中准确分配字节,然后将数组的第一部分复制到b
. 问题是b
有一个size
字段说 10 但根本没有任何元素的内存。
If you still want to be able to compile this in C, then you must resolve the warning by closing your eyes: silent that specific warning. If you only need C++ compatibility, you can manually disable copy construction and assignment:
如果您仍然希望能够在 C 中编译它,那么您必须通过闭上眼睛来解决警告:使特定警告静音。如果您只需要 C++ 兼容性,您可以手动禁用复制构造和赋值:
struct runtime_array {
int size;
char data[1];
private:
runtime_array( runtime_array const & ); // undefined
runtime_array& operator=( runtime_array const & ); // undefined
};
By declaring the copy constructor and assignment operator the compiler will not generate one for you (and won′t complain about it not knowing how). By having the two private you will get compile time errors if by mistake you try to use it in code. Since they are never called, they can be left undefined --this is also used to avoid calling it from within a different method of the class, but I assume that there are no other methods.
通过声明复制构造函数和赋值运算符,编译器不会为您生成一个(并且不会抱怨它不知道如何生成)。通过将这两个私有化,如果您错误地尝试在代码中使用它,您将得到编译时错误。由于它们从未被调用,因此它们可以保持未定义——这也用于避免从类的不同方法中调用它,但我假设没有其他方法。
Since you are refactoring to C++, I would also make the default constructor private and provide a static public inlined method that will take care of the proper allocation of the contents. If you also make the destructor private you can make sure that user code does not try to call delete
on your objects:
由于您正在重构为 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
};
This will ensure that user code does not by mistake create your objects in the stack nor will it mix calls to malloc/free
with calls to new/delete
, since you manage creation and destruction of your objects. None of this changes affects the memory layout of your objects.
这将确保用户代码不会错误地在堆栈中创建您的对象,也不会混淆对 的调用和malloc/free
对 的调用new/delete
,因为您管理对象的创建和销毁。这些更改都不会影响对象的内存布局。
[*] The calculation for the size here is a bit off, and will overallocate, probably by as much as sizeof(int)
as the size of the object has padding at the end.
[*] 这里的大小计算有点偏离,并且会过度分配,可能sizeof(int)
与对象的大小最后填充一样多。
回答by Nordic Mainframe
If this is a MSVC compiler (which is what the warning message tells me), then you can disable this warning using #pragma warning, ie.:
如果这是一个 MSVC 编译器(这是警告消息告诉我的),那么您可以使用 #pragma 警告禁用此警告,即:
#pragma warning( push )
#pragma warning( disable : 4200 )
struct _TREEDATSTR
{
BYTE btLen;
DWORD dwModOff;
BYTE btPat[0];
};
#pragma warning( pop )
BTW, the message about the copy-constructor is not creepy, but a good thingbecause it means, that you can't copy instances of _TREEDATSTR without the unknown bytes in btPat: The compiler has no idea how big _TREEDATSTR really is (because of the 0-size array) and therefore refuses to generate a copy constructor. This means, that you can't do this:
顺便说一句,关于复制构造函数的消息并不令人毛骨悚然,但这是一件好事,因为这意味着,如果没有 btPat 中的未知字节,您就无法复制 _TREEDATSTR 的实例:编译器不知道 _TREEDATSTR 到底有多大(因为0 大小的数组),因此拒绝生成复制构造函数。这意味着,你不能这样做:
_TREEDATSTR x=y;
which shouldn't work anyway.
这无论如何都行不通。
回答by dreamlax
Try changing it to say btPat[1]
instead. I think both C++ and C standards dictate that an array cannot have 0 elements. It could cause problems for any code that rely on the size of the _TREEDATSTR
struct itself, but usually these sorts of structs are typecast from buffers where (in this case) the first byte of the buffer determines how many bytes are actually in btPat
. This kind of approach relies on the fact that there is no bounds checking on C arrays.
尝试将其更改为说btPat[1]
。我认为 C++ 和 C 标准都规定数组不能有 0 个元素。它可能会导致任何依赖于_TREEDATSTR
结构本身大小的代码出现问题,但通常这些类型的结构是从缓冲区类型转换的,其中(在这种情况下)缓冲区的第一个字节决定了实际中有多少字节btPat
。这种方法依赖于没有对 C 数组进行边界检查的事实。
回答by Brian Hooper
If it's complaining about the copy constructor and assignment operator functions, couldn't you supply your own. If you don't want them, declare them private.
如果它抱怨复制构造函数和赋值运算符函数,你不能提供你自己的。如果您不想要它们,请将它们声明为私有。
This may produce a lot of errors elsewhere in the code if you are assigning or copying without realising it, in which case it wouldn't have worked anyway because there are no automatically generated ones.
如果您在没有意识到的情况下进行分配或复制,这可能会在代码的其他地方产生很多错误,在这种情况下,它无论如何都不会起作用,因为没有自动生成的错误。
回答by 6502
The main idea for this in C is to get for _TREEDATSTR elements the needed extra memory; in other words allocation will be done with malloc(sizeof(_TREEDATSTR) + len).
C 中的主要思想是为 _TREEDATSTR 元素获取所需的额外内存;换句话说,分配将使用 malloc(sizeof(_TREEDATSTR) + len) 完成。
Pragma pack is used to ask the compiler to leave no empty spaces between the fields (normally compilers do sometimes leave some unused bytes between fields of structres to guarantee alignment because in many modern processors this is a huge speed improvement).
Pragma pack 用于要求编译器在字段之间不留空白(通常编译器有时会在结构的字段之间留下一些未使用的字节以保证对齐,因为在许多现代处理器中,这是一个巨大的速度改进)。
Note however that there are architectures where unaligned access is not just slow... but totally forbidden (segfault) so those compilers are free to ignore the pragma pack; code that uses pragma pack is inherently unportable.
但是请注意,有些架构中未对齐的访问不仅速度慢……而且完全被禁止(段错误),因此这些编译器可以自由地忽略编译指示包;使用 pragma pack 的代码本质上是不可移植的。
I think I would have put the dword first in the structure, and this probably wouldn't have required a pragma pack; also a way to silence the warning is to allocate a one element array and doing the allocation using (len-1) extra bytes.
我想我会把 dword 放在结构的第一位,这可能不需要编译指示包;还有一种消除警告的方法是分配一个元素数组并使用 (len-1) 额外字节进行分配。
In C++ all this stuff is quite dangerous, because you're basically fooling the compiler into thinking that the size of the object is smaller than it really is, and given that C++ is a copy-logic language this means asking for troubles (for example for copy construction and assignment functions that will act only on the first part of the object). For everyday use it's surely MUCH better to use for example an std::vector instead, but this of course will come at an higher price (double indirection, more memory for every _TREEDATSTR instance).
在 C++ 中,所有这些东西都非常危险,因为你基本上是在愚弄编译器,让编译器认为对象的大小比实际大小要小,而且鉴于 C++ 是一种复制逻辑语言,这意味着要自找麻烦(例如用于仅作用于对象的第一部分的复制构造和赋值函数)。对于日常使用,使用例如 std::vector 肯定要好得多,但这当然会以更高的价格(双间接,每个 _TREEDATSTR 实例有更多内存)。
I normally don't like thinking all other programmers are idiots, so if this kind of bad trickery has been used then probably there is a well paying reason for it... For a definitive judgment however a much deeper inspection would be needed.
我通常不喜欢认为所有其他程序员都是白痴,所以如果使用了这种糟糕的伎俩,那么可能有一个很好的理由......然而,为了做出明确的判断,需要更深入的检查。
To summarize:
总结一下:
- Using a zero element array at the end of an array is a trick used to create variable-sized objects. The allocation is done by requesting sizeof(structure) + n*sizeof(array_element) bytes.
- Pragma pack is used to tell the compiler to avoid adding extra padding bytes between structure fields. This is needed when a precise control on the memory layout is needed (for example because those object are being accessed by hand-written assembly)
- Don't do that in C++ unless you really need it and you know what you're doing
- 在数组末尾使用零元素数组是用于创建可变大小对象的技巧。分配是通过请求 sizeof(structure) + n*sizeof(array_element) 字节来完成的。
- Pragma pack 用于告诉编译器避免在结构字段之间添加额外的填充字节。当需要对内存布局进行精确控制时需要这样做(例如,因为这些对象正在由手写程序集访问)
- 不要在 C++ 中这样做,除非你真的需要它并且你知道你在做什么
There is no way to "correctly" silence the warning because the code wants to play dirty (and C++ compilers don't like to be fooled about object size). If you use this object inside other objects, or as a base for other objects, or pass it aroud by value then whatever bad happens you asked for it.
没有办法“正确地”使警告静音,因为代码想要玩弄脏(而且 C++ 编译器不喜欢被对象大小所愚弄)。如果你在其他对象中使用这个对象,或者作为其他对象的基础,或者通过值传递它,那么无论发生什么坏事,你都会要求它。
回答by degski
Although I realise that this is an old thread, I would like to give my pure c++11 solution to the OP's question. The idea is to wrap the to be allocated object, adding in the padding to align the objects in array to power of 2 adresses, in the following way:
虽然我意识到这是一个旧线程,但我想为 OP 的问题提供我的纯 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 )> { };
The objects padding size can be calculated at compile-time with the following class (returns L if L is power of 2, else the next power of 2 gt L):
可以在编译时使用以下类计算对象填充大小(如果 L 是 2 的幂,则返回 L,否则返回 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 };
};