std::sort with struct containing Eigen datatype causes segfault
Asked Answered
B

0

10

I have the following issue when compiling in release mode using MSVC 19.31.31106.2 (and C++17, as far as I understand the eigen page, alignment should be not an issue anymore) that I get a segfault from within of std::sort. The official Eigen 3.4 release is used.

The following compiler flags are set:

/errorReport:prompt /WX /Zc:forScope /GR /arch:AVX2 /Gd /MD /std:c++17 /GS /W4 /wd"4389" /wd"4996" /wd"4244" /wd"4324" /wd"4127" /Zc:wchar_t  /EHsc /nologo /Gm- /O2 /Ob2 /Zc:inline /fp:precise /D "_MBCS" /D "WIN32" /D "_WINDOWS" /D "NDEBUG" /D "_CRT_SECURE_NO_WARNINGS"  

So I have the following struct:

struct MyStruct
{
    Eigen::Matrix<double, 2, 1> mVecA{}; 
    Eigen::Matrix<double, 2, 1> mVecB{};  
    bool mA = true;
    uint64_t mB = 0;
};

Then after filling a vector std::vector<MyStruct> myVec; with some data the call to

std::sort(myVec.begin(), myVec.end(), [](const MyStruct& lhs, const MyStruct& rhs) { return lhs.mB < rhs.mB; });

causes a segfault (whereas using std::stable_sort causes no issue) it seems to be thrown from inside of std::move. In debug mode this does not happen. Further when changing the order of MyStruct to

struct MyStruct
{
    bool mA = true;
    uint64_t mB = 0;
    Eigen::Matrix<double, 2, 1> mVecA{}; 
    Eigen::Matrix<double, 2, 1> mVecB{};  
};

the segfault does not appear anymore at all. I don't really understand why this order would be an issue? Shouldn't it also be preferable to avoid unecessary padding within the struct?

Another thing that could fix it would be to explicitly defie alignment for the eigen matrices:

struct MyStruct
{
    alignas(32) Eigen::Matrix<double, 2, 1> mVecA{}; 
    alignas(32) Eigen::Matrix<double, 2, 1> mVecB{};  
    bool mA = true;
    uint64_t mB = 0;
};

Here is a minimal example (AVX2 is enabled in the CMake and Eigen needs to be included): Here is an godbolt example: https://godbolt.org/z/odned4axW. (It only works when /MD is not added to the flags, otherwise it will not run at all.. which is different from the behaviour on my local machine, where it can still run with /MD but causes an exception in the move constructor)

#include <Eigen/Core>
#include <Eigen/Dense>
#include <Eigen/StdVector>
#include <stdint.h>
#include <algorithm>
#include <stdlib.h> 
#include <iostream> 

int main()
{
    struct MyStruct
    {
        MyStruct() = default;
        MyStruct(const Eigen::Matrix<double, 2, 1>& vecA, const Eigen::Matrix<double, 2, 1>& vecB, uint64_t value, bool boo) : mVecA(vecA), mVecB(vecB), mValue(value), mBool(boo) {}

        alignas(32) Eigen::Matrix<double, 2, 1> mVecA{};
        alignas(32) Eigen::Matrix<double, 2, 1> mVecB{};
        uint64_t mValue = 0;
        bool mBool = true;
    };

    std::vector<MyStruct> mSorted;
    for (uint64_t i = 0; i < 1000; i++)
        mSorted.emplace_back(Eigen::Matrix<double, 2, 1>::Random(), Eigen::Matrix<double, 2, 1>::Random(), static_cast<uint64_t>(rand()), true);

    std::sort(mSorted.begin(), mSorted.end(), [](const MyStruct& lhs, const MyStruct& rhs) { return lhs.mValue < rhs.mValue; });

    std::cout << mSorted[0].mValue << std::endl;
    std::cout << "Super test" << std::endl;
}

The error occurs in PlainObjectBase.h line 496 (move constructor of PlainObjectBase) traced from (std::sort -> std::_Sort_unchecked -> std::_Partition_by_median_guess_unchecked -> std::iter_swap -> std::swap).

Buddhi answered 30/5, 2022 at 7:35 Comment(14)
smells like UB. Please post a minimal reproducible exampleInnerve
Usually on such cases the defect is in what you address most briefly like "filling with some data".Scotsman
Your question is closed, but it is super interesting! Add a minimal example. Take what you have and scale everything you can back until it doesn't break anymore. Then add that, and it will be very cool to figure out what is going on. At that point, we can REOPEN it!!!!Sandstone
I added a small example, if alignas(32) is removed the crash happens otherwise it runs fine for me.Buddhi
Appears to work with latest clang/gcc (and latest Eigen): godbolt.org/z/61oaevvGE Can you be more specific what compiler (version) and what Eigen version you are using? Also show all compile flags.Aretina
@Aretina I added the versions for compiler (MSVC 19.31.31106.2) and Eigen 3.4 Also I supplied the compiler options for MSVC compiling.Buddhi
Removing the /MD parameter makes it work: godbolt.org/z/s3KxzEsGa (No idea/didn't look up what that does).Aretina
@Aretina MD does dynamic runtime linkage. /MT links runtime statically. It is not a good idea to mix libraries with different runtime linkage. For instance, one could pass an allocated object to another unit that uses a different runtime. Upon destruction the other allocator might deallocate a memory address which it does not have an access to (hence Segmentation Fault)Wallboard
how about you post a call stack when the segfault happens?Wallboard
I added the call stack to the description (move constructor of eigen object fails within std::swap).Buddhi
Your godbolt example seems broken in the first place. The program crashes even with uncommented code godbolt.org/z/M985GrhE7Wallboard
I updated it, it runs now without /MD and gives output for me. But with /MD i cannot get it to run at all (this behaviour seems different from my local machine)Buddhi
I can't reproduce the issue locally with the provided compiler flags and the example code at the bottom of the post (although with cl.exe 19.32 and 19.29). Is this really the code that is crashing for you? Also, no idea why the godbolt code with /MD does not run: The returned error code 3221225781 is 0xC0000135 in hex, meaning STATUS_DLL_NOT_FOUND. Apparently, a necessary library is not installed there?Advocaat
I know the eigen docs specifically says otherwise...but have you tried the steps here to align the vector allocator: eigen.tuxfamily.org/dox/group__TopicStlContainers.html?Sumikosumma

© 2022 - 2024 — McMap. All rights reserved.