Search code examples
c++debuggingvisual-c++undefined-behavior

Different behavior in debug and release


In my custom ArrayProxy class. I have this test.

    "array_proxy_initializer_list"_test = [] {
        ArrayProxy<int> array = { 1, 2, 3, 4, 5 };
        expect(array.size() == 5);
        expect(array[0] == 1);
        expect(array[1] == 2);
        expect(array[2] == 3);
        expect(array[3] == 4);
        expect(array[4] == 5);
        LogInfo{array[0]};// in release log: 1166876000 
        LogInfo{array[1]};// in release log: 32758
    };

In debug mode. It work fine. And in release mode. It fails.

Here are my ArrayProxy class.

template<typename T>
    concept ContainerObject = requires(T t) {
        t.data();
        t.size();
    };

    template<typename T>
    class ArrayProxy
    {
    public:
        using value_type = T;

        constexpr ArrayProxy() : m_count(0), m_ptr(nullptr) {}
        constexpr ArrayProxy(std::nullptr_t) : m_count(0), m_ptr(nullptr) {}
        ArrayProxy(const T& value) : m_count(1), m_ptr(&value) {}
        ArrayProxy(std::size_t count, const T* ptr) : m_count(count), m_ptr(ptr) {}
        ArrayProxy(const std::initializer_list<T>& args) : m_count(args.size()), m_ptr(args.begin()) {}
        ArrayProxy(ContainerObject auto container) : m_count(container.size()), m_ptr(container.data()) {}

        template<std::size_t N>
        ArrayProxy(const T (&ptr)[N]) : m_count(N), m_ptr(ptr) {};

        const T& operator[](int i) const
        {
            return m_ptr[i];
        }

        std::size_t size() const
        {
            return m_count;
        }

        const T* data() const
        {
            return m_ptr;
        }

    private:
        const T*    m_ptr;
        std::size_t m_count;
    };

In my debug. I find a interesting behavior.

If I swap the order of m_ptr and m_count.

    private:
        std::size_t m_count;
        const T*    m_ptr;
    };

In both debug and release mode. It works fine.
I don't know why, Is there a ub in my code?

compile on msvc 2022 and c++20.


Solution

  • Thanks for @Peter. I know how to test this class. The std::initializer_list's life time if from function's call to return.

    This class is used to wrap some c api likevoid c_api(int* data, int size);. Use this class is friendly with modern cpp containers.

    For example.

    void c_api_wrapped(const ArrayProxy<int>& arr)
    {
        c_api(arr.data(), arr.size());
    }
    
    void test()
    {
        c_api_wrapped(1);
        c_api_wrapped({1,2,3});
        std::vector<int> arr = {1,2,3};
        c_api_wrapped(arr);
    }
    

    It is friendly for these temporary value called.

    But my use-case is in a wrong way. This class can only use in function's param.

    If I use this as a variable in other way. The std::initializer_list would be destroyed.

    So I should change my test to:

    void test_array_proxy(const rain::ArrayProxy<int>& arr, const std::vector<int>& ground_truth)
    {
        boost::ut::expect(arr.size() == ground_truth.size());
        for(int i = 0; i < arr.size(); ++i)
        {
            boost::ut::expect(arr[i] == ground_truth[i]);
        }
    }
    
    int main()
    {
        "array_proxy_initializer_list"_test = [] {
            std::vector<int> ground_truth = { 1, 2, 3, 4, 5 };
            test_array_proxy({ 1, 2, 3, 4, 5 }, ground_truth);
        };
    }
    

    It will work fine and it show this class's usage.

    As I mentioned this class is from vulkan-hpp. The vulkan-hpp also provided a `ArrayProxyNoTemporaries" to prevent these situation.

    Thanks for your help!