Eigen::aligned_allocator missing allocate_at_least
Summary
When Eigen::aligned_allocator is used under certain circumstances, a segmentation fault occurs.
Environment
- Operating System : Ubuntu 24.10
- Architecture : x64
- Eigen Version : 3.4.0 as well as current master branch
- Compiler Version : tested with clang 19.1.1, but likely not limited to this one
- Compile Flags : -std=c++23 -stdlib=libc++ and, optionally, -DEIGEN_MAX_ALIGN_BYTES=32 or -mavx
- Vector Extension : optionally AVX
Minimal Example
#include <Eigen/Core>
int main() {
std::vector<Eigen::Vector4f, Eigen::aligned_allocator<Eigen::Vector4f>> vec;
vec.resize(10); // memory is allocated
vec[0].x() = 1.0; // use the vector (may not be necessary, but compiler must not optimize away the memory (de)allocation)
} // vec is destroyed and memory deallocated
Steps to reproduce
- Compile code with
clang++ Eigen_allocate_at_least.cpp -o Eigen_allocate_at_least -std=c++23 -stdlib=libc++ -I/path/to/eigen. Important is that the code is compiled as C++23, and that libc++ (the LLVM standard library) is used, not libstdc++ (the GNU standard library). Optionally,-DEIGEN_MAX_ALIGN_BYTES=32or-mavxcan be added to produce a variation of the error - Execute, preferably with valgrind
What is the current bug behavior?
Without -DEIGEN_MAX_ALIGN_BYTES=32 or -mavx, valgrind reports:
Mismatched free() / delete / delete []
==12706== at 0x484B8BF: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==12706== by 0x10A620: Eigen::internal::aligned_free(void*) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x10A5EC: Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >::deallocate(Eigen::Matrix<float, 4, 1, 0, 4, 1>*, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x10A4B4: std::__1::allocator_traits<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::deallocate[abi:ne190101](Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&, Eigen::Matrix<float, 4, 1, 0, 4, 1>*, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x10AABD: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::__destroy_vector::operator()[abi:ne190101]() (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x1093A7: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::~vector[abi:ne190101]() (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x109268: main (in /home/markus/Eigen_allocate_at_least)
==12706== Address 0x4d12040 is 0 bytes inside a block of size 160 alloc'd
==12706== at 0x4848FD3: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==12706== by 0x10A304: void* std::__1::__libcpp_operator_new[abi:ne190101]<unsigned long>(unsigned long) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x10A282: std::__1::__libcpp_allocate[abi:ne190101](unsigned long, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x10A1D3: std::__1::allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >::allocate[abi:ne190101](unsigned long) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x10A16C: std::__1::allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >::allocate_at_least[abi:ne190101](unsigned long) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x10A12C: std::__1::allocation_result<Eigen::Matrix<float, 4, 1, 0, 4, 1>*, unsigned long> std::__1::allocator_traits<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::allocate_at_least[abi:ne190101]<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >(Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x10A08C: auto std::__1::__allocate_at_least[abi:ne190101]<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >(Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x1097E1: std::__1::__split_buffer<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&>::__split_buffer(unsigned long, unsigned long, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x109554: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::__append(unsigned long) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x10930B: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::resize(unsigned long) (in /home/markus/Eigen_allocate_at_least)
==12706== by 0x109232: main (in /home/markus/Eigen_allocate_at_least)
With -DEIGEN_MAX_ALIGN_BYTES=32 or -mavx, valgrind reports:
==14616== Invalid read of size 1
==14616== at 0x10A637: Eigen::internal::handmade_aligned_free(void*) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A614: Eigen::internal::aligned_free(void*) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A5EC: Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >::deallocate(Eigen::Matrix<float, 4, 1, 0, 4, 1>*, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A4B4: std::__1::allocator_traits<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::deallocate[abi:ne190101](Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&, Eigen::Matrix<float, 4, 1, 0, 4, 1>*, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10AAFD: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::__destroy_vector::operator()[abi:ne190101]() (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x1093A7: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::~vector[abi:ne190101]() (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x109268: main (in /home/markus/Eigen_allocate_at_least)
==14616== Address 0x4d1203f is 1 bytes before a block of size 160 alloc'd
==14616== at 0x4848FD3: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==14616== by 0x10A304: void* std::__1::__libcpp_operator_new[abi:ne190101]<unsigned long>(unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A282: std::__1::__libcpp_allocate[abi:ne190101](unsigned long, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A1D3: std::__1::allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >::allocate[abi:ne190101](unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A16C: std::__1::allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >::allocate_at_least[abi:ne190101](unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A12C: std::__1::allocation_result<Eigen::Matrix<float, 4, 1, 0, 4, 1>*, unsigned long> std::__1::allocator_traits<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::allocate_at_least[abi:ne190101]<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >(Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A08C: auto std::__1::__allocate_at_least[abi:ne190101]<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >(Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x1097E1: std::__1::__split_buffer<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&>::__split_buffer(unsigned long, unsigned long, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x109554: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::__append(unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10930B: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::resize(unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x109232: main (in /home/markus/Eigen_allocate_at_least)
==14616==
==14616== Mismatched free() / delete / delete []
==14616== at 0x484B8BF: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==14616== by 0x10A661: Eigen::internal::handmade_aligned_free(void*) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A614: Eigen::internal::aligned_free(void*) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A5EC: Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >::deallocate(Eigen::Matrix<float, 4, 1, 0, 4, 1>*, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A4B4: std::__1::allocator_traits<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::deallocate[abi:ne190101](Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&, Eigen::Matrix<float, 4, 1, 0, 4, 1>*, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10AAFD: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::__destroy_vector::operator()[abi:ne190101]() (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x1093A7: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::~vector[abi:ne190101]() (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x109268: main (in /home/markus/Eigen_allocate_at_least)
==14616== Address 0x4d12040 is 0 bytes inside a block of size 160 alloc'd
==14616== at 0x4848FD3: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==14616== by 0x10A304: void* std::__1::__libcpp_operator_new[abi:ne190101]<unsigned long>(unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A282: std::__1::__libcpp_allocate[abi:ne190101](unsigned long, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A1D3: std::__1::allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >::allocate[abi:ne190101](unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A16C: std::__1::allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >::allocate_at_least[abi:ne190101](unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A12C: std::__1::allocation_result<Eigen::Matrix<float, 4, 1, 0, 4, 1>*, unsigned long> std::__1::allocator_traits<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::allocate_at_least[abi:ne190101]<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >(Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10A08C: auto std::__1::__allocate_at_least[abi:ne190101]<Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >(Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&, unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x1097E1: std::__1::__split_buffer<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&>::__split_buffer(unsigned long, unsigned long, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> >&) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x109554: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::__append(unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x10930B: std::__1::vector<Eigen::Matrix<float, 4, 1, 0, 4, 1>, Eigen::aligned_allocator<Eigen::Matrix<float, 4, 1, 0, 4, 1> > >::resize(unsigned long) (in /home/markus/Eigen_allocate_at_least)
==14616== by 0x109232: main (in /home/markus/Eigen_allocate_at_least)
I think the root cause is that Eigen::aligned_allocator does not provide an allocate_at_least function (function introduced in C++23). The libc++ implementation of std::vector seems to use allocate_at_least to allocate memory. Since Eigen::aligned_allocator does not provide the function, the inherited one from std::allocator is used instead. When deallocating the memory however, the deallocate from Eigen::aligned_allocator is used. Now, depending on whether handmade_aligned_malloc/free is used (-DEIGEN_MAX_ALIGN_BYTES=32 or -mavx flags), there is either a mismatch between operator new and free, or additionally the invalid read in handmade_aligned_free.
As far as I know, this problem so far does not happen with libstdc++ because allocate_at_least is not used there, but this may change at any time.
What is the expected correct behavior?
No invalid read, no mismatched free/delete, memory is allocated by Eigen::aligned_allocator::allocate_at_least.
Relevant logs
See https://github.com/PointCloudLibrary/pcl/issues/6215 for an example of this problem occurring "in the wild".
Anything else that might help
-
Have a plan to fix this issue. Adding allocate_at_leasttoEigen::aligned_allocatorshould fix the problem. Likely, this can be nearly identical to the existingallocatefunction. If you like this approach, I can do further tests and open a merge request. Thanks for reading!