আদিম স্ট্যাটিক_ভেক্টর বাস্তবায়নে সম্ভাব্য অপরিজ্ঞাত আচরণ


12

tl; dr: আমার মনে হয় আমার স্ট্যাটিক_ভেক্টরটির আচরণের সংজ্ঞা নেই তবে আমি এটি খুঁজে পাচ্ছি না।

এই সমস্যাটি মাইক্রোসফ্ট ভিজ্যুয়াল সি ++ 17 এ I এটি একটি সি ++ 17 প্রোগ্রাম, স্ট্যান্ড :: অ্যালাইনড_স্টোরেজ এবং স্ট্যান্ড :: লন্ডার ব্যবহার করে। আমি এটিকে নীচে সে অংশগুলিতে সিদ্ধ করার চেষ্টা করেছি যা আমি মনে করি এটি সমস্যার সাথে প্রাসঙ্গিক:

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-বাইট ফুবার স্ট্রাক্টগুলি স্ট্যাকের একটি স্ট্যাটিক_ভেক্টারে অনুলিপি করি, তারপরে আমরা প্রথম সদস্য হিসাবে 8-বাইট স্ট্রাক্টের একটি স্ট্যাটিক_ভেক্টরের একটি জোড় তৈরি করি এবং দ্বিতীয় হিসাবে একটি uint64_t করি। আমি যাচাই করতে পারি যে জোড়গুলি তৈরি হওয়ার আগে অবধি স্টেপটিতে সঠিক মান রয়েছে the এবং ... এই জুটিটি নির্মাণের সময় স্ট্যাটিক_ভেক্টরের অনুলিপি নির্মাণকারী (যা কলিং ফাংশনে অন্তর্ভুক্ত করা হয়েছে) এর মধ্যে অপ্টিমাইজেশান সক্ষম হওয়া এই সেগফাল্টগুলি।

দীর্ঘ গল্প সংক্ষিপ্ত, আমি বিচ্ছিন্নতা পরিদর্শন। এটি যেখানে জিনিসগুলি কিছুটা অদ্ভুত হয়; ইনলাইনড কপি কন্সট্রাক্টরের চারপাশে উত্পন্ন উত্সটি নীচে দেখানো হয়েছে - নোট করুন যে এটি আসল কোড থেকে এসেছে, উপরের নমুনাটি নয়, যা বেশ কাছাকাছি তবে জুটির নির্মাণের উপরে আরও কিছু স্টাফ রয়েছে:

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-বিট মুভ অপারেশন ব্যবহার করে, তারপরে নিবন্ধ থেকে মেমোরিতে রেকর্ড করি। তবে মজার কিছু আছে - যেখানে আমরা উত্স অবজেক্ট থেকে পড়ার জন্য [ইবিপি + এডেক্স * 8 +] এর মতো কিছু থেকে মুভ আশা করব, তার পরিবর্তে ঠিক আছে ... [ইক্যেক্স]। এটি ঠিক শোনাচ্ছে না। এক্সেক্সের মান কী?

দেখা যাচ্ছে, ইএক্সএক্সে কেবল একটি আবর্জনা ঠিকানা রয়েছে, আমরা সেগফাল্ট করছি ing কোথা থেকে এই মানটি পেল? এখানে তাত্ক্ষণিকভাবে 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)]  

এটি দেখতে নিয়মিত পুরানো সিডিসিএল ফাংশন কলের মতো। প্রকৃতপক্ষে, ফাংশনটির ঠিক উপরে উপরে একটি বহিরাগত সি ফাংশনে কল রয়েছে। তবে নোট কী হচ্ছে তা: লক্ষণীয় স্ট্যাকের উপর আর্গুমেন্ট চাপার জন্য ইক্যেক্স অস্থায়ী রেজিস্টার হিসাবে ব্যবহৃত হচ্ছে, ফাংশনটি আহ্বান করা হয়েছে, এবং ... তবে উত্স স্ট্যাটিক_ভেক্টর থেকে পড়ার জন্য নীচে ভ্রান্তভাবে ব্যবহার না করা অবধি ই্যাক্স আবার কখনও স্পর্শ করা হবে না।

অনুশীলনে, বাস্তুতন্ত্রের বিষয়বস্তু এখানে ডাকা ফাংশন দ্বারা ওভাররাইট হয়ে যায়, এটি অবশ্যই করার অনুমতি দেওয়া হয়। তবে তা না পারলেও বাস্তবে এখানে কোনও সঠিক জিনিসটির কোনও ঠিকানা নেই এমন উপায় নেই - সর্বোপরি, এটি স্থানীয় স্ট্যাক সদস্যকে নির্দেশ করবে যা স্থির_ভেক্টর নয়। দেখে মনে হচ্ছে সংকলকটি কিছু বগাস অ্যাসেমব্লি নির্গত করেছে। এই ফাংশনটি কখনই সঠিক আউটপুট উত্পাদন করতে পারে না

আমি এখন যেখানে তাই এখন। অদ্ভুত সমাবেশ যখন স্ট্যান্ড :: লন্ডার ল্যান্ডের চারপাশে খেলার সময় অপ্টিমাইজেশন সক্ষম করা হয় তখন অপরিজ্ঞাত আচরণের মতো গন্ধ আমার কাছে আসে। তবে কোথা থেকে আসতে পারে তা আমি দেখতে পাচ্ছি না। পরিপূরক হলেও প্রান্তিকভাবে দরকারী তথ্য হিসাবে, ডান পতাকাগুলির সাথে ঝাঁকুনি এটির সাথে একই জাতীয় সমাবেশ তৈরি করে, যদি না এটি সঠিকভাবে মূল্য পড়ার জন্য এক্সেক্সের পরিবর্তে ebp + edx ব্যবহার করে।


কেবল একটি অলৌকিক দৃষ্টিভঙ্গি তবে আপনি clear()যে সংস্থানগুলিতে ফোন করেছেন সেগুলিতে কেন আপনি কল করছেন std::move?
বাথশেবা

কীভাবে প্রাসঙ্গিক তা আমি দেখছি না। অবশ্যই, একই আকারের সাথে স্থির_ভেক্টর ছেড়ে যাওয়া বৈধ হবে তবে সরানো-আউট অবজেক্টগুলির একগুচ্ছ। স্থির_ভেক্টর ডিস্ট্রাক্টর যেভাবে চলতে থাকলে সামগ্রীগুলি ধ্বংস হয়ে যাবে। তবে আমি শূন্যের আকার সহ সরানো-আউট ভেক্টরটি ছেড়ে যেতে পছন্দ করি।
pjohansson

হাম। আমার পে-গ্রেডের বাইরেও। এটি ভাল জিজ্ঞাসিত হিসাবে একটি উত্সাহ আছে, এবং মনোযোগ আকর্ষণ করতে পারে।
বাথশেবা

আপনার কোড দিয়ে কোনও ক্রাশ পুনরুত্পাদন করতে পারে না (অভাবের কারণে এটি সংকলন করে না বলে সাহায্য করা হয় না is_iterator) দয়া করে একটি ন্যূনতম পুনরুত্পাদনযোগ্য উদাহরণ সরবরাহ করুন
এলান বার্টলস

1
বিটিডব্লু, আমি মনে করি প্রচুর কোড এখানে অপ্রাসঙ্গিক। আমি বলতে চাইছি আপনি এখানে কোথাও অ্যাসাইনমেন্ট অপারেটরকে কল করবেন না যাতে এটি উদাহরণ থেকে মুছে ফেলা যায়
বার্টপ

উত্তর:


6

আমি মনে করি আপনার একটি সংকলক বাগ আছে। যোগ __declspec( noinline )করা operator[]ক্র্যাশটি সংশোধন করে বলে মনে হচ্ছে :

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

আপনি মাইক্রোসফ্টকে বাগটি রিপোর্ট করার চেষ্টা করতে পারেন তবে ভিজ্যুয়াল স্টুডিও 2019-এ বাগটি ইতিমধ্যে ঠিক হয়ে গেছে বলে মনে হচ্ছে।

অপসারণ std::launderকরায় ক্র্যাশটি ঠিক হয়ে গেছে বলে মনে হচ্ছে:

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

আমি অন্যান্য ব্যাখ্যায়ও কম চলছি। আমাদের বর্তমান পরিস্থিতি যতটুকু সাফল্য অর্জন করেছে, এটি প্রশংসনীয় বলে মনে হচ্ছে এটিই যা ঘটছে, তাই আমি এটি গ্রহণযোগ্য উত্তর হিসাবে চিহ্নিত করব।
pjohansson

লন্ডার অপসারণ এটি ঠিক করে দেয়? লন্ডার অপসারণ করা স্পষ্টভাবে অপরিজ্ঞাত আচরণ হবে! স্ট্রেঞ্জ।
pjohansson

@ পজোহানসন std::launderকিছু বাস্তবায়ন দ্বারা ভুলভাবে প্রয়োগ করা হয়েছিল বলে জানা গেছে। হতে পারে আপনার এমএসভিএসের সংস্করণটি সেই ভুল প্রয়োগের উপর ভিত্তি করে। দুর্ভাগ্যক্রমে আমার কাছে সূত্র নেই।
ফিউরিশ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.