Dan Albert
Dan Albert

Reputation: 10509

double free without any dynamic memory allocation

In general, what could cause a double free in a program that is does not contain any dynamic memory allocation?

To be more precise, none of my code uses dynamic allocation. I'm using STL, but it's much more likely to be something I did wrong than for it to be a broken implmentation of G++/glibc/STL.

I've searched around trying to find an answer to this, but I wasn't able to find any example of this error being generated without any dynamic memory allocations.

I'd love to share the code that was generating this error, but I'm not permitted to release it and I don't know how to reduce the problem to something small enough to be given here. I'll do my best to describe the gist of what my code was doing.

The error was being thrown when leaving a function, and the stack trace showed that it was coming from the destructor of a std::vector<std::set<std::string>>. Some number of elements in the vector were being initialized by emplace_back(). In a last ditch attempt, I changed it to push_back({{}}) and the problem went away. The problem could also be avoided by setting the environment variable MALLOC_CHECK_=2. By my understanding, that environment variable should have caused glibc to abort with more information rather than cause the error to go away.

This question is only being asked to serve my curiosity, so I'll settle for a shot in the dark answer. The best I have been able to come up with is that it was a compiler bug, but it's always my fault.

Upvotes: 3

Views: 964

Answers (2)

fvdnabee
fvdnabee

Reputation: 573

I extracted a presentable example showcasing the fault I made that led to the "double free or corruption" runtime error. Note that the struct doesn't explicitly use any dynamic memory allocations, however internally std::vector does (as its content can grow to accommodate more items). Therefor, this issue was a bit hard to diagnose as it doesn't violate 'the rule of 3' principle.

#include <vector>
#include <string.h>

typedef struct message {
  std::vector<int> options;
  void push(int o) { this->options.push_back(o); }
} message;

int main( int argc, const char* argv[] )
{
  message m;
  m.push(1);
  m.push(2);

  message m_copy;
  memcpy(&m_copy, &m, sizeof(m));
  //m_copy = m; // This is the correct method for copying object instances, it calls the default assignment operator generated 'behind the scenes' by the compiler
}

When main() returns m_copy is destroyed, which calls the std::vector destructor. This tries to delete memory that was already freed when the m object was destroyed.

Ironically, I was actually using memcpy to try and achieve a 'deep copy'. This is where the fault lies in my case. My guess is that by using the assignment operator, all the members of message.options are actually copied to "newly allocated memory" whereas memcpy would only copy those members that were allocated at compile time (e.g. a uint32_t size member). See Will memcpy or memmove cause problems copying classes?. Obviously this also applies to structs with non-fundamental typed members (as is the case here).

Maybe you also copied a std::vector incorrectly and saw the same behavior, maybe you didn't. In the end, it was my entirely my fault :).

Upvotes: 1

billz
billz

Reputation: 45450

In general, what could cause a double free in a program that is does not contain any dynamic memory allocation?

Normally when you make a copy of a type which dynamically allocates memory but doesn't follow rule of three

struct Type
{
   Type() : ptr = new int(3) { }
   ~Type() { delete ptr; }
   // no copy constructor is defined
   // no copy assign operator is defined

private:
   int * ptr;
};

void func()
{       
   { 
     std::vector<Type> objs;
     Type t; // allocates ptr
     objs.push_back(t); // make a copy of t, now t->ptr and objs[0]->ptr point to same memory location
     // when this scope finishes, t will be destroyed, its destructor will be called and it will try to delete ptr;
     // objs go out of scope, elements in objs will be destroyed, their destructors are called, and delete ptr; will be executed again. That's double free on same pointer.
   }    
}

Upvotes: 3

Related Questions