Destructor of Ref to const VectorXd calls free(nullptr)

Summary

The destructor of an Eigen::Ref<const Eigen::VectorXd> calls free(nullptr).

Environment

  • Operating System : Linux
  • Architecture : x64
  • Eigen Version : Version: 3.4.0-2ubuntu2
  • Compiler Version : Gcc 12.3
  • Compile Flags : none
  • Vector Extension : none

Minimal Example

#include <Eigen/Dense>
#include <malloc.h>
#include <dlfcn.h>
#include <iostream>

int __malloc_calls = 0;
int __free_calls = 0;
bool __register_calls = false;

void reset_counters()
{
    __register_calls = true;
    __malloc_calls = 0;
    __free_calls = 0;
}

std::pair<int, int> get_counters()
{
    __register_calls = false;
    return std::make_pair(__malloc_calls, __free_calls);
}

void* malloc(size_t sz)
{
    static auto libc_malloc = reinterpret_cast<void *(*)(size_t)>(dlsym(RTLD_NEXT, "malloc"));

    if(!__register_calls) return libc_malloc(sz);

    __malloc_calls++;

    return libc_malloc(sz);
}

void free(void *p)
{
    static auto libc_free = reinterpret_cast<void(*)(void*)>(dlsym(RTLD_NEXT, "free"));

    if (!__register_calls)
        return libc_free(p);

    __free_calls++;

    fprintf(stderr, "free(%p)\n", p);

    libc_free(p);
}


int main()
{
    Eigen::VectorXd a(10);

    std::cerr << "const case: \n";

    reset_counters();
    {
        Eigen::Ref<const Eigen::VectorXd> aref = a;
    }
    auto ct = get_counters();

    std::cerr << "n malloc called " << ct.first << "\n";
    std::cerr << "n free   called " << ct.second << "\n";

    std::cerr << "non-const case: \n";
    reset_counters();
    {
        Eigen::Ref<Eigen::VectorXd> aref = a;
    }
    ct = get_counters();

    std::cerr << "n malloc called " << ct.first << "\n";
    std::cerr << "n free   called " << ct.second << "\n";
}

Steps to reproduce

  1. save the MRE as eigen_ref_calls_free.cpp
  2. compile with g++ eigen_ref_calls_free.cpp -I/usr/include/eigen3 -ldl
  3. execute with ./a.out
  4. the output is
const case: 
free((nil))
n malloc called 0
n free   called 1
non-const case: 
n malloc called 0
n free   called 0

What is the current bug behavior?

The destructor of the Eigen Ref to a const Eigen::VectorXd calls free with null pointer argument. Notice that the non-const version does not call free on destruction.

What is the expected correct behavior?

If an Eigen Ref is not managing any memory (as in this case, where a temporary copy is obviously not generated), I do not expect it interact with the memory manager in any way. Notice that on some systems this actually causes issues. For instance, in the Xenomai3 real-time co-kernel, calls to malloc/free inside a real-time context will cause a mode switch to non-realtime mode.