gpt4 book ai didi

c++ - 原始 static_vector 实现中可能未定义的行为

转载 作者:行者123 更新时间:2023-12-01 11:55:43 29 4
gpt4 key购买 nike

tl; dr:我认为我的 static_vector 有未定义的行为,但我找不到它。

这个问题是在 Microsoft Visual C++ 17 上。我有这个简单且未完成的 static_vector 实现,即具有固定容量的 vector ,可以堆栈分配。这是一个 C++17 程序,使用 std::aligned_storage 和 std::launder。我试图将其归结为我认为与该问题相关的部分:

template <typename T, size_t NCapacity>
class static_vector
{
public:
typedef typename std::remove_cv<T>::type value_type;
typedef size_t size_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;

static_vector() noexcept
: count()
{
}

~static_vector()
{
clear();
}

template <typename TIterator, typename = std::enable_if_t<
is_iterator<TIterator>::value
>>
static_vector(TIterator in_begin, const TIterator in_end)
: count()
{
for (; in_begin != in_end; ++in_begin)
{
push_back(*in_begin);
}
}

static_vector(const static_vector& in_copy)
: count(in_copy.count)
{
for (size_type i = 0; i < count; ++i)
{
new(std::addressof(storage[i])) value_type(in_copy[i]);
}
}

static_vector& operator=(const static_vector& in_copy)
{
// destruct existing contents
clear();

count = in_copy.count;
for (size_type i = 0; i < count; ++i)
{
new(std::addressof(storage[i])) value_type(in_copy[i]);
}

return *this;
}

static_vector(static_vector&& in_move)
: count(in_move.count)
{
for (size_type i = 0; i < count; ++i)
{
new(std::addressof(storage[i])) value_type(move(in_move[i]));
}
in_move.clear();
}

static_vector& operator=(static_vector&& in_move)
{
// destruct existing contents
clear();

count = in_move.count;
for (size_type i = 0; i < count; ++i)
{
new(std::addressof(storage[i])) value_type(move(in_move[i]));
}

in_move.clear();

return *this;
}

constexpr pointer data() noexcept { return std::launder(reinterpret_cast<T*>(std::addressof(storage[0]))); }
constexpr const_pointer data() const noexcept { return std::launder(reinterpret_cast<const T*>(std::addressof(storage[0]))); }
constexpr size_type size() const noexcept { return count; }
static constexpr size_type capacity() { return NCapacity; }
constexpr bool empty() const noexcept { return count == 0; }

constexpr reference operator[](size_type n) { return *std::launder(reinterpret_cast<T*>(std::addressof(storage[n]))); }
constexpr const_reference operator[](size_type n) const { return *std::launder(reinterpret_cast<const T*>(std::addressof(storage[n]))); }

void push_back(const value_type& in_value)
{
if (count >= capacity()) throw std::out_of_range("exceeded capacity of static_vector");
new(std::addressof(storage[count])) value_type(in_value);
count++;
}

void push_back(value_type&& in_moveValue)
{
if (count >= capacity()) throw std::out_of_range("exceeded capacity of static_vector");
new(std::addressof(storage[count])) value_type(move(in_moveValue));
count++;
}

template <typename... Arg>
void emplace_back(Arg&&... in_args)
{
if (count >= capacity()) throw std::out_of_range("exceeded capacity of static_vector");
new(std::addressof(storage[count])) value_type(forward<Arg>(in_args)...);
count++;
}

void pop_back()
{
if (count == 0) throw std::out_of_range("popped empty static_vector");
std::destroy_at(std::addressof((*this)[count - 1]));
count--;
}

void resize(size_type in_newSize)
{
if (in_newSize > capacity()) throw std::out_of_range("exceeded capacity of static_vector");

if (in_newSize < count)
{
for (size_type i = in_newSize; i < count; ++i)
{
std::destroy_at(std::addressof((*this)[i]));
}
count = in_newSize;
}
else if (in_newSize > count)
{
for (size_type i = count; i < in_newSize; ++i)
{
new(std::addressof(storage[i])) value_type();
}
count = in_newSize;
}
}

void clear()
{
resize(0);
}

private:
typename std::aligned_storage<sizeof(T), alignof(T)>::type storage[NCapacity];
size_type count;
};

这似乎工作正常一段时间。然后,有一次,我正在做与此非常相似的事情 - 实际代码更长,但它的要点如下:
struct Foobar
{
uint32_t Member1;
uint16_t Member2;
uint8_t Member3;
uint8_t Member4;
}

void Bazbar(const std::vector<Foobar>& in_source)
{
static_vector<Foobar, 8> valuesOnTheStack { in_source.begin(), in_source.end() };

auto x = std::pair<static_vector<Foobar, 8>, uint64_t> { valuesOnTheStack, 0 };
}

换句话说,我们首先将 8 字节 Foobar 结构复制到堆栈上的 static_vector 中,然后我们将 8 字节结构的 static_vector 的 std::pair 作为第一个成员,将 uint64_t 作为第二个成员。我可以在构造对之前立即验证 valuesOnTheStack 包含正确的值。并且...在构造对时,在 static_vector 的复制构造函数(已内联到调用函数中)中启用了优化的段错误。

长话短说,我检查了拆卸。这是事情变得有点奇怪的地方;内联复制构造函数周围生成的 asm 如下所示 - 请注意,这是来自实际代码,而不是上面的示例,它非常接近,但在对构造之上还有一些东西:
00621E45  mov         eax,dword ptr [ebp-20h]  
00621E48 xor edx,edx
00621E4A mov dword ptr [ebp-70h],eax
00621E4D test eax,eax
00621E4F je <this function>+29Ah (0621E6Ah)
00621E51 mov eax,dword ptr [ecx]
00621E53 mov dword ptr [ebp+edx*8-0B0h],eax
00621E5A mov eax,dword ptr [ecx+4]
00621E5D mov dword ptr [ebp+edx*8-0ACh],eax
00621E64 inc edx
00621E65 cmp edx,dword ptr [ebp-70h]
00621E68 jb <this function>+281h (0621E51h)

好的,首先我们有两条 mov 指令将 count 成员从源复制到目标;到目前为止,一切都很好。 edx 被归零,因为它是循环变量。然后我们快速检查 count 是否为零;它不为零,因此我们继续执行 for 循环,在该循环中我们使用两个 32 位 mov 操作首先从内存复制到寄存器,然后从寄存器复制到内存。但是有一些可疑的东西-我们期望从 [ebp+edx*8+] 之类的东西中读取 mov 从源对象中读取,而只是... [ecx]。这听起来不对。 ecx 的值(value)是什么?

事实证明,ecx 只包含一个垃圾地址,与我们正在执行段错误的地址相同。它从哪里得到这个值?这是上面的asm:
00621E1C  mov         eax,dword ptr [this]  
00621E22 push ecx
00621E23 push 0
00621E25 lea ecx,[<unrelated local variable on the stack, not the static_vector>]
00621E2B mov eax,dword ptr [eax]
00621E2D push ecx
00621E2E push dword ptr [eax+4]
00621E31 call dword ptr [<external function>@16 (06AD6A0h)]

这看起来像一个普通的旧 cdecl 函数调用。实际上,该函数调用了上面的外部 C 函数。但请注意正在发生的事情:ecx 被用作临时寄存器以将参数压入堆栈,调用该函数,然后…… ecx 永远不会再被触及,直到它在下面被错误地用于从源 static_vector 中读取。

实际上,ecx 的内容会被这里调用的函数覆盖,这当然是允许的。但即使没有,ecx 也不可能在此处包含正确事物的地址 - 充其量,它会指向一个不是 static_vector 的本地堆栈成员。似乎编译器发出了一些虚假的程序集。此函数永远无法产生正确的输出。

所以这就是我现在的位置。在 std::launder 中玩耍时启用优化时的奇怪组装对我来说就像未定义的行为。但我看不出这可能来自哪里。作为补充但很少有用的信息,带有正确标志的 clang 产生与此类似的程序集,除了它正确使用 ebp+edx 而不是 ecx 来读取值。

最佳答案

我认为你有一个编译器错误。添加 __declspec( noinline )operator[]似乎修复了崩溃:

__declspec( noinline ) constexpr const_reference operator[]( size_type n ) const { return *std::launder( reinterpret_cast<const T*>( std::addressof( storage[ n ] ) ) ); }

您可以尝试向 Microsoft 报告该错误,但该错误似乎已在 Visual Studio 2019 中修复。

删除 std::launder似乎也修复了崩溃:
constexpr const_reference operator[]( size_type n ) const { return *reinterpret_cast<const T*>( std::addressof( storage[ n ] ) ); }

关于c++ - 原始 static_vector 实现中可能未定义的行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60613286/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com