TL;DR: This is just a small article regarding C realloc()
and how I fell into a small trap. I thought I would document it here as I imagine it’s relatively rare for people to come across.
I was writing a function using realloc()
that would concatenate strings together. a
was the string to be extended and b
is to be copied onto the end. Simples. It was in the following format:
0001 char* cc(char* a, char* b)
realloc()
is a C function, similar to malloc()
, but reallocates memory, and copies the existing data. It’s exceptionally useful. As the documentation suggests, it is used like follows:
0002 void* realloc (void* ptr, size_t size);
Simple enough. Except, in some cases (particularly the more complex ones) I was getting:
0003 free(): double free detected in tcache 2 0004 Aborted (core dumped)
Eh?
So here is the original code:
0005 char* cc(char* a, char* b){ 0006 int l = strlen(a); 0007 int n = strlen(b) + 1; 0008 a = realloc(a, l + n); 0009 memcpy(a + l, b, n); 0010 return a; 0011 }
It compiles fine, with no warnings. The idea is that I could use it like this:
0012 char* a = malloc(256); 0013 sprintf(a, "%s", "test"); 0014 char* b = "ing"; 0015 char* c = cc(a, b);
And this is pretty much how I wrote it in my tests. No matter what I did, this would always pass, as I expected it to.
I should at this point mention I am writing an ultra small JSON parser, and space is a premium (as is part of the challenge). For this reason, I am looking to save space in the source code in order to meet some arbitrary limitations to make the problem more interesting.
For this reason, I would use a shortcut like so:
0016 char* a = malloc(256); 0017 sprintf(a, "%s", "test"); 0018 char* b = "ing"; 0019 //char* c = cc(a, b); 0020 cc(a, b);
Some may see my error already. This worked in my initial tests, with a
now contacting the result of the cc()
operation. But as the test cases got more complex, I was seeing more random segfaults. Weird.
Let’s look back at that documentation again (emphasis mine):
Changes the size of the memory block pointed to by ptr.
The function may move the memory block to a new location (whose address is returned by the function).
Oops. For the small test cases, it was re-using the memory address as it was large enough to contain the concatenated strings. For the larger test cases, it had to allocate a new memory block. This can be shown with:
0021 char* a = malloc(256); 0022 sprintf(a, "%s", "test"); 0023 char* b = "ing"; 0024 char* c = cc(a, b); 0025 if(a == c){ 0026 /* Smaller concats do not need a new memory block */ 0027 }else{ 0028 /* Larger concats usually do need a new memory block */ 0029 }
Of course, I didn’t want to have to write c = cc(a, b)
in all of my code (due to space requirements), so I wrote this instead:
0030 char* cc(char** a, char* b){ 0031 int l = strlen(*a); 0032 int n = strlen(b) + 1; 0033 *a = realloc(*a, l + n); 0034 memcpy(*a + l, b, n); 0035 return *a; 0036 }
We now use a pointer to the location of the a
pointer and update this! Now I update each cc(a, b)
with cc(&a, b)
- job done!
Read the documentation, carefully-er.
Hopefully this helps somebody out there!