tl; dr: मुझे लगता है कि मेरे static_vector के पास अपरिभाषित व्यवहार है, लेकिन मैं इसे नहीं ढूँढ सकता।
यह समस्या Microsoft Visual C ++ 17 पर है। मेरे पास यह सरल और अधूरा static_vector कार्यान्वयन है, अर्थात एक निश्चित क्षमता वाला वेक्टर जो स्टैक आवंटित किया जा सकता है। यह एक C ++ 17 प्रोग्राम है, जो std :: align_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 };
}
दूसरे शब्दों में, हम पहले स्टैक पर एक static_vector में 8-बाइट फ़ॉबर ट्रैक्स की नकल करते हैं, फिर हम एक स्टैड बनाते हैं: 8-बाइट स्ट्रक्चर्स के स्टैटिक_वेक्टर की जोड़ी पहले सदस्य के रूप में, और दूसरे के रूप में एक uinto_t। मैं यह सत्यापित कर सकता हूं कि मानों का निर्माण किया गया है। स्टैक में जोड़ी बनाने से ठीक पहले सही मान शामिल हैं। और ... इस segfaults को 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)
ठीक है, इसलिए पहले हमारे पास स्रोत से गंतव्य तक गिनती सदस्य की नकल करने के दो दो निर्देश हैं; अब तक तो सब ठीक है। edx शून्य है क्योंकि यह लूप वेरिएबल है। अगर गिनती शून्य है तो हमारे पास एक त्वरित जांच है; यह शून्य नहीं है, इसलिए हम लूप के लिए आगे बढ़ते हैं जहां हम 8-बाइट संरचना को कॉपी करते हैं, जिसमें दो 32-बिट मूवमेंट का उपयोग मेमोरी से रजिस्टर करने के लिए पहले किया जाता है, फिर रजिस्टर से मेमोरी में। लेकिन कुछ गड़बड़ है - जहाँ हम स्रोत वस्तु से पढ़ने के लिए [ebp + edx * 8 +] जैसी किसी चीज़ की अपेक्षा करेंगे, वहाँ इसके बजाय बस ... [ecx] है। यह सही नहीं लगता। Ecx का मूल्य क्या है?
पता चला है, एक्जॉक्स में सिर्फ एक कचरा पता होता है, वही जिसे हम सेगफॉल्ट कर रहे हैं। इसे यह मूल्य कहां से मिला? यहाँ तुरंत ऊपर है:
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)]
यह एक नियमित पुराने सीडीसीएल फ़ंक्शन कॉल की तरह दिखता है। दरअसल, फंक्शन में ऊपर की तरफ बाहरी C फंक्शन का कॉल होता है। लेकिन ध्यान दें कि क्या हो रहा है: स्टैक पर तर्कों को आगे बढ़ाने के लिए एक अस्थायी रजिस्टर के रूप में ecx का उपयोग किया जा रहा है, फ़ंक्शन को आमंत्रित किया गया है, और ... तब तक ecx को फिर से कभी नहीं छुआ जाता है जब तक कि स्रोत static_vector से पढ़ने के लिए नीचे गलत तरीके से उपयोग नहीं किया जाता है।
व्यवहार में, एक्क्स की सामग्री को यहां बुलाए गए फ़ंक्शन द्वारा अधिलेखित कर दिया जाता है, जिसे यह निश्चित रूप से करने की अनुमति है। लेकिन यहां तक कि अगर यह नहीं था, तो कोई रास्ता नहीं है, यहां तक कि एक्स्टेक्स कभी भी यहां सही चीज़ के लिए एक पता रखने वाला है - सबसे अच्छा, यह एक स्थानीय स्टैक सदस्य को इंगित करेगा जो static_vector नहीं है। ऐसा लगता है जैसे संकलक ने कुछ फर्जी असेंबली का उत्सर्जन किया है। यह फ़ंक्शन कभी भी सही आउटपुट नहीं दे सकता है ।
इसलिए मैं अब वहीं हूं। अजीब असेंबली जब एसटीडी में आस-पास खेलते समय अनुकूलन सक्षम होते हैं :: लॉन्ड्रर भूमि मुझे अपरिभाषित व्यवहार की तरह बदबू आती है। लेकिन मैं यह नहीं देख सकता कि यह कहाँ से आ रहा है। पूरक लेकिन मामूली रूप से उपयोगी जानकारी के रूप में, सही झंडे के साथ क्लैंग इस के समान विधानसभा का उत्पादन करता है, सिवाय इसके कि मूल्यों को पढ़ने के लिए ईएक्सएक्स के बजाय ईबीपी + एडक्स का सही उपयोग करता है।
is_iterator
) कृपया एक न्यूनतम प्रतिलिपि प्रस्तुत करने योग्य उदाहरण
clear()
उन संसाधनों पर क्यों कॉल कर रहे हैं जिन पर आपने कॉल किया हैstd::move
?