tl; dr: Sanırım static_vector'um tanımlanmamış bir davranışa sahip, ama bulamıyorum.
Bu sorun, Microsoft Visual C ++ 17 üzerinde. Bu basit ve bitmemiş static_vector uygulama, yani yığın tahsis edilebilir sabit kapasiteli bir vektör var. Bu std :: aligned_storage ve std :: launder kullanan bir C ++ 17 programıdır. Soruyla alakalı olduğunu düşündüğüm bölümlere aşağıda kaynatmaya çalıştım:
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;
};
Bu bir süre iyi sonuç verdi. Sonra, bir noktada, buna çok benzer bir şey yapıyordum - gerçek kod daha uzun, ama bu onun özüne varıyor:
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 };
}
Başka bir deyişle, ilk önce 8 baytlık Foobar yapılarını yığındaki bir static_vector'a kopyalarız, daha sonra birinci üye olarak 8 baytlık bir static_vector std :: çifti ve ikinci olarak uint64_t yaparız. Çifti oluşturulmadan hemen önce valuOnTheStack'ın doğru değerleri içerdiğini doğrulayabilirim. Ve ... bu segment çifti kurarken static_vector'ın kopya yapıcısında (çağıran işlevin içine yerleştirilmiş) etkinleştirilmiş optimizasyona sahip.
Uzun lafın kısası, demontajı kontrol ettim. İşlerin biraz garipleştiği yer; satır içi kopya yapıcısı etrafında oluşturulan asm aşağıda gösterilmiştir - bunun gerçek koddan, yukarıdaki örnekten değil, oldukça yakın ancak çift yapısının üzerinde daha fazla şey olduğuna dikkat edin:
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)
Tamam, bu yüzden önce sayım elemanını kaynaktan hedefe kopyalayan iki mov komutumuz var; çok uzak çok iyi. edx, döngü değişkeni olduğu için sıfırlanır. Daha sonra sayımın sıfır olup olmadığını hızlıca kontrol ederiz; sıfır değil, bu nedenle for-döngüsüne geçiyoruz, burada 8 baytlık yapıyı önce 32 bitlik iki işlem kullanarak bellekten kayda, sonra kayıttan belleğe kopyalıyoruz. Ama balık gibi bir şey var - burada [ebp + edx * 8 +] gibi bir şeyden bir mov'un kaynak nesneden okunmasını bekleriz, bunun yerine sadece ... [ecx]. Kulağa doğru gelmiyor. Ecx'in değeri nedir?
Görünen o ki, ecx sadece bir çöp adresi içeriyor, aynı segfatasyon yaptığımız adres. Bu değeri nereden aldı? İşte hemen yukarıdaki 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)]
Bu normal bir eski cdecl işlev çağrısı gibi görünüyor. Aslında, fonksiyonun hemen üstünde harici bir C fonksiyonuna çağrı vardır. Ama ne olduğuna dikkat edin: ecx, yığında argümanları itmek için geçici bir kayıt olarak kullanılıyor, işlev çağrılıyor ve ... sonra ecx, static_vector kaynağından okumak için yanlışlıkla kullanılıncaya kadar bir daha asla dokunulmuyor.
Uygulamada, ecx içeriğinin üzerine, burada adı verilen işlevle yazılır, ki bu elbette yapmasına izin verilir. Ancak, olmasa bile, ecx'in burada doğru olana bir adres içermesinin bir yolu yoktur - en iyi durumda, static_vector olmayan yerel bir yığın üyesine işaret eder. Derleyici sahte bir derleme yaymış gibi görünüyor. Bu işlev asla doğru çıktıyı üretemez.
İşte şimdi ben buradayım. Std :: launder arazi oyun oynarken optimizasyon etkinleştirildiğinde garip montaj bana tanımsız davranış gibi kokuyor. Ama bunun nereden gelebileceğini göremiyorum. Tamamlayıcı fakat marjinal olarak yararlı bilgi olarak, doğru bayraklara sahip clang, buna benzer montaj üretir, ancak değerleri okumak için ecx yerine ebp + edx'i doğru kullanır.
is_iterator
) lütfen minimal tekrarlanabilir bir örnek
clear()
kaynaklarıstd::move
arıyorsun?