C++ 这是黑客删除别名警告UB?

C++ 这是黑客删除别名警告UB?,c++,gcc,compiler-warnings,placement-new,strict-aliasing,C++,Gcc,Compiler Warnings,Placement New,Strict Aliasing,我们刚刚将编译器升级到GCC4.6,现在我们得到了一些警告。目前,我们的代码库还没有处于使用c++0x编译的状态,而且无论如何,我们不想在prod中运行它(至少还没有),所以我需要一个修复程序来删除这个警告 出现警告的原因通常如下: struct SomeDataPage { // members char vData[SOME_SIZE]; }; 稍后,这将按以下方式使用 SomeDataPage page; new(page.vData) SomeType(); // non-tr

我们刚刚将编译器升级到GCC4.6,现在我们得到了一些警告。目前,我们的代码库还没有处于使用c++0x编译的状态,而且无论如何,我们不想在prod中运行它(至少还没有),所以我需要一个修复程序来删除这个警告

出现警告的原因通常如下:

struct SomeDataPage
{
  // members
  char vData[SOME_SIZE];
};
稍后,这将按以下方式使用

SomeDataPage page;
new(page.vData) SomeType(); // non-trivial constructor
例如,要读取、更新和返回,通常会发生以下转换

reinterpret_cast<SomeType*>(page.vData)->some_member();
这似乎可以正常工作(请参见这里的简单示例:)并且不会生成任何警告,在c++0x之前使用它(不是在风格上)可以吗

注意:我一般不想使用
-fno严格别名

编辑:似乎我弄错了,4.4上也有相同的警告,我想我们只是在最近的更改中才注意到这一点(这不太可能是编译器问题),但问题仍然存在

编辑:进一步的调查产生了一些有趣的信息,如果代码分成以下两行,那么在一行中执行强制转换和调用成员函数似乎是导致警告的原因

SomeType* ptr = reinterpret_cast<SomeType*>(page.vData);
ptr->some_method();
SomeType*ptr=reinterpret\u cast(page.vData);
ptr->some_method();
这实际上不会产生警告。因此,我在ideone上的简单示例是有缺陷的,更重要的是,我上面的hack没有修复警告,修复警告的唯一方法是将函数调用从cast中分离出来-然后可以将cast作为一个
重新解释\u cast
为什么不使用:

SomeType *item = new (page.vData) SomeType();
然后:

item->some_member ();
我不认为工会是最好的方式,它也可能有问题。从gcc文件中:

`-fstrict-aliasing'
 Allows the compiler to assume the strictest aliasing rules
 applicable to the language being compiled.  For C (and C++), this
 activates optimizations based on the type of expressions.  In
 particular, an object of one type is assumed never to reside at
 the same address as an object of a different type, unless the
 types are almost the same.  For example, an `unsigned int' can
 alias an `int', but not a `void*' or a `double'.  A character type
 may alias any other type.

 Pay special attention to code like this:
      union a_union {
        int i;
        double d;
      };

      int f() {
        a_union t;
        t.d = 3.0;
        return t.i;
      }
 The practice of reading from a different union member than the one
 most recently written to (called "type-punning") is common.  Even
 with `-fstrict-aliasing', type-punning is allowed, provided the
 memory is accessed through the union type.  So, the code above
 will work as expected.  However, this code might not:
      int f() {
        a_union t;
        int* ip;
        t.d = 3.0;
        ip = &t.i;
        return *ip;
      }

这与您的问题之间的关系很难确定。我猜编译器在
SomeType
中看到的数据与
vData
中的数据不一样,坦白说,我更关心的是一些大小不够大的问题。但是,使用
char*
别名任何类型都是合法的。因此,只需执行
reinterpret_cast(&page.vData[0])
就可以了

还有,我对这种设计提出质疑。除非您正在实现
boost::variant
或类似的东西,否则没有太多理由使用它

struct SomeDataPage
{
  // members
  char vData[SOME_SIZE];
};
由于混叠/对齐的原因,这是有问题的。首先,此结构的对齐方式不一定与您试图在其中双关的类型相同。您可以尝试使用GCC属性强制执行特定对齐:

struct SomeDataPage { char vData[SOME_SIZE] __attribute__((aligned(16))); };
在这里,16的对齐对于我遇到的任何事情都是足够的。同样,编译器仍然不喜欢您的代码,但如果对齐良好,它不会中断。或者,您可以使用新的C++0x alignof/alignas

template <class T>
struct DataPage {
   alignof(T) char vData[sizeof(T)];
};
模板
结构数据页{
(T)字符vData[sizeof(T)]的对齐;
};
您可以尝试:

SomeDataPage page;
SomeType *data = new(page.vData) SomeType(); // non-trivial constructor
data->some_member();

我认为在执行
new(page.vData)SomeType()
之后,与
page.vData
位于同一位置的对象的实际类型是
SomeType
,因为这是存储在那里的最后一个对象,因此类型pun是合法的。不过,由演员阵容触发的警告可能无法确定您是否已经完成了新的安置。如果我认为您的原始代码实际上是正确的,那么假设GCC中没有bug,那么可怕的黑客攻击也应该是正确的(因为GCC对
reinterpret\u cast
的定义使得生成的指针相同)。不确定,不过。@Steve,似乎我错了,警告也出现在4.4中——只是没有得到重视。关于黑客的问题仍然存在。“这实际上并没有产生警告。因此,“使用哪些选项?@curiousguy,
-Wall
,如果我没记错的话。@Nim您可能希望通过一些优化再试一次。@Nim:有什么理由不这样做吗?”?可能有一个虚拟构造函数,它不会覆盖
vData
,并且在您想要使用它时只是在原地新建。有一些我没有提到的复杂情况,
SomeType
实际上是一个变体,我想利用这样一个事实,分配给变量所持有的相同类型比构造默认类型(然后重构新类型)便宜。用
char*
别名任何类型都是合法的,但用任何类型别名
char*
都不一定合法。类型双关规则相对于对象的实际类型以及用于访问该对象的类型不是对称的。虽然类型别名规则通常不是对称的,但它们相对于
char
是对称的——您可以使用
char
对任何类型进行别名,也可以使用任何类型对
char
进行别名。由于此代码与
char
有别名,编译器警告不正确。@Chris:检查3.10/15。它并不是说动态类型为
char
的对象可以通过任何类型的左值访问。
SomeType
的各种情况都可以,但唯一符合条件的UDT是具有
char
有符号char
无符号char
成员的聚合或联合,或其中一个的cv符合条件的版本
SomeType
很可能不是这样的集合或联合体。没有对称性。因此,这个别名是合法的,因为由于新的位置,对象的动态类型是
SomeType
。如果标准中有其他地方允许任何其他别名,我就错过了。”史蒂夫杰索普:我认为这是标准中的一个错误。总有一种方法可以让非类型化的内存有一个特定数量的字节。如果没有,则需要创建一种经批准的方法。如果没有其他的东西,它实际上不可能在C++中实现分配器。@ OnFiFaul:在这里有一个尴尬的地方——放置<代码>新< /Cord>给了你一个方法来将原始内存(<代码> char < /代码>)转换成你的类型<代码>
SomeDataPage page;
new(page.vData) SomeType(); // non-trivial constructor
reinterpret_cast<SomeType*>(page.vData)->some_member();
warning: type punned pointer will break strict-aliasing rules
SomeDataPage page;
SomeType *data = new(page.vData) SomeType(); // non-trivial constructor
data->some_member();