GCC 7,aligned_storage和"解除引用类型惩罚指针将破坏严格别名规则"

时间:2017-04-30 21:35:32

标签: c++ gcc strict-aliasing gcc7

我写的代码在GCC 4.9,GCC 5和GCC 6中没有警告。它也是一些较旧的GCC 7实验快照(例如7-20170409)的警告。但是在最近的快照(包括第一个RC)中,它开始产生关于别名的警告。代码基本归结为:

#include <type_traits>

std::aligned_storage<sizeof(int), alignof(int)>::type storage;

int main()
{
    *reinterpret_cast<int*>(&storage) = 42;
}

使用最新的GCC 7 RC编译:

$ g++ -Wall -O2 -c main.cpp
main.cpp: In function 'int main()':
main.cpp:7:34: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
  *reinterpret_cast<int*>(&storage) = 42;

(有趣的是,当禁用优化时不会产生警告)

使用GCC 6编译完全没有警告。

现在我想知道,上面的代码肯定是打字型,对此毫无疑问,但是不是std::aligned_storage意味着以这种方式使用?

例如,给定here的示例代码通常不会对GCC 7发出警告,但仅仅是因为:

  • std::string不知何故不受影响,
  • 使用偏移量访问
  • std::aligned_storage

std::string更改为int,删除对std::aligned_storage的偏移访问权限并删除不相关的部分即可获得此内容:

#include <iostream>
#include <type_traits>
#include <string>

template<class T, std::size_t N>
class static_vector
{
    // properly aligned uninitialized storage for N T's
    typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
    std::size_t m_size = 0;

public:

    // Access an object in aligned storage
    const T& operator[](std::size_t pos) const
    {
        return *reinterpret_cast<const T*>(data/*+pos*/); // <- note here, offset access disabled
    }
};

int main()
{
    static_vector<int, 10> v1;
    std::cout << v1[0] << '\n' << v1[1] << '\n';
}

这会产生完全相同的警告:

main.cpp: In instantiation of 'const T& static_vector<T, N>::operator[](std::size_t) const [with T = int; unsigned int N = 10; std::size_t = unsigned int]':
main.cpp:24:22:   required from here
main.cpp:17:16: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
         return *reinterpret_cast<const T*>(data/*+pos*/);
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

所以我的问题是 - 这是一个错误还是一个功能?

2 个答案:

答案 0 :(得分:3)

我无法回答是否由于别名或警告无理由而确实存在未定义行为的可能性。我发现别名主题是一个相当复杂的雷区。

但是,我认为您的代码的以下变体消除了别名问题而没有任何开销(并且可能更具可读性)。

#include <iostream>
#include <type_traits>
#include <string>

template<class T, std::size_t N>
class static_vector
{
    // properly aligned uninitialized storage for N T's
    union storage_t_ {
        T item;
        typename std::aligned_storage<sizeof(T), alignof(T)>::type aligned_member;
    };
    storage_t_ data[N];

    std::size_t m_size = 0;

public:

    // Access an object in aligned storage
    const T& operator[](std::size_t pos) const
    {
        return data[0].item;
    }
};

int main()
{
    static_vector<int, 10> v1;
    std::cout << v1[0] << '\n' << v1[1] << '\n';
}

对于你的情况是否可以接受,我无法确定。

答案 1 :(得分:1)

您的代码会导致未定义的行为(尽管警告文本与根本原因有点相关)。在C ++中,概念 storage objects 是不同的东西。物体占据存储空间;但存储可能不存在任何对象。

aligned_storage机制为存储提供了无对象。您可以使用placement-new在其中创建对象。但是,您的代码在不包含任何对象的存储上使用赋值运算符。如果您查阅赋值运算符的定义,您会发现它没有创建对象的规定;事实上,它只定义当左侧指定已存在的对象时会发生什么。

main中的代码应为:

new(&storage) int(42);

请注意,由于我们在这里处理原始类型,因此不需要执行任何形式的析构函数调用,并且您可以在同一空间上多次调用placement-new而不会出现问题。

标准的[basic.life]部分讨论了对于不包含对象的存储可以做什么,以及如果对存储中存在的对象使用placement-new或析构函数调用会发生什么。

另见this answer

cppreference aligned_storage中的代码是正确的。您提供了一些基于您描述的错误代码&#34;删除不相关的部分&#34;但是您删除了一个非常相关的部分,即调用placement-new来在存储中创建对象:

new(data+m_size) T(std::forward<Args>(args)...);

return *reinterpret_cast<const T*>(data+pos);是有效索引时写pos是正确的,并且表达式访问先前放置新调用创建的对象。