Search code examples
c++eigen

Eigen static lib aligned_free "double free or corruption"


This is a continuation of an earlier post.

But this time with a hopefully better example. This simple test crashes when setting a vector.

I am using Ubuntu 20.04, gcc 9.3.0, c++17, eigen 3.3.7

main.cpp

#include <iostream>
#include <memory>
#include "C.h"

using namespace std;

class B { 
public:
    C c;

    B() {
        cout << (uint64_t)this << endl;
    }
    ~B(){}

};

int main() {

    shared_ptr<B> b = make_shared<B>();

    b->c.v = VectorXd(5); // << crashes here
    // SIGABRT on `Eigen::internal::aligned_free` when trying to do `std::free()`

    return 0; 
}

Class C is linked as a static library. The static library is compiled in Release mode (--std=c++17 -DNDEBUG -O3), while the main program is run in Debug - without optimizations. If both the static lib and the main program are run in Debug, then there is no crash. Also, if they are both run in Release, there is no crash. The problem only occurs when the static lib is compiled in Release and the main program in Debug.

C.h

#pragma once
#include <vector>
#include <Eigen/Core>

using namespace std;
using namespace Eigen;

class C {
public:

    VectorXd v;

    C();
    ~C();
};

C.cpp

#include "C.h"

C::C() {
    v = VectorXd(5);
}

C::~C() {

}

Solution

  • In case you compiled the library and the main executable with mismatching architecture flags (-m... flags for gcc), you can observe the crash. For example, when I compile the library with avx enabled (-mavx) and the main executable without avx, or vice versa, it crashes. The crash also occurs in Eigen 3.4.0. Moreover, it also occurs without optimization (-O0).

    The Eigen aligned_malloc() and aligned_free() functions are doing different things depending on the value of the EIGEN_DEFAULT_ALIGN_BYTES macro, which has a different value when e.g. AVX (among others) is enabled or disabled. Eigen automatically selects the proper value depending on the used compiler flags. So, if you enable AVX only in the static library, the constructor of C uses handmade_aligned_malloc() internally, which is not returning the pointer received by malloc() directly but instead moves it a bit to ensure proper alignment. On the other hand, assigning a new value to C::v from the executable (which is compiled without AVX) will first try to free the memory by calling b->c.v.~VectorXd(), which then ends up calling just free() instead of handmade_aligned_free(). Thus, free() receives an address that does not point to the start of the allocated memory, which then causes the crash. Of course, if you enable AVX in the executable and disable it in the library, you get mismatching calls to malloc() and handmade_aligned_free() instead, also resulting in a crash.

    The solution is simple: Ensure that you compile the static library and the executable with the same architecture flags. If for whatever reason this is not possible, ensure that creation and destruction always happens in the same component. For example, you could make C::v private and allow modification of it only via functions that are implemented in the C.cpp file.

    Note: Your other post is a bit different since it involves no dynamic memory management, while in the present post you use VectorXd which does use dynamic memory management.

    Note 2: The described problem is unrelated to the use of std::shared_ptr. But for Eigen versions before 3.4.0 you might need to use EIGEN_MAKE_ALIGNED_OPERATOR_NEW to ensure that the pointer b is properly aligned, regardless of the architecture and C++17; at least the original Eigen issue was implemented only for 3.4.0. But I might be wrong on this.