Coffee Space 
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!