5

我编写的代码在 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应该这样使用吗?

例如,此处给出的示例代码通常不会对 GCC 7 产生警告,但这仅仅是因为:

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

通过更改std::stringint,删除偏移访问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*/);
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

4

2 回答 2

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';
}

你的情况是否可以接受,我不能确定。

于 2017-05-01T18:37:01.870 回答
1

您的代码会导致未定义的行为(尽管警告文本与根本原因有点相切)。在 C++ 中,存储对象的概念是不同的东西。对象占用存储;但是存储可能没有对象存在。

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

你的代码main应该是:

new(&storage) int(42);

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

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

另请参阅此答案


cppreference aligned_storage 中的代码是正确的。您根据您描述为“删除不相关部分”的内容提供了一些不正确的代码,但是您删除了一个非常相关的部分,即调用placement-new 以在存储中创建对象:

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

那么写return *reinterpret_cast<const T*>(data+pos);whenpos是一个有效的索引是正确的,并且该表达式访问由先前的placement-new调用创建的对象。

于 2017-07-01T01:20:39.263 回答