Coffee Space


Listen:

C Realloc

Preview Image

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?

Implementation

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.

Solution

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!

Lessons Learned

Read the documentation, carefully-er.

Hopefully this helps somebody out there!