Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically allocating and copying an array

I sometimes see code like this:

char* copyStr(char* input) {
  int inputLength;
  char *answer;

  inputLength = strlen(input);

  answer = malloc(inputLength + 1);
  answer = input;

  return answer;
}

People often say this code doesn't work and that this pattern

answer = malloc(inputLength + 1);
answer = input;

makes no sense. Why is it so? To my eye, the code is OK. It allocates the right amount of memory for the answer, and then copies the input to the answer. And it seems to work in my tests, for example

int main()
{
   printf ("%s\n", copyStr("Hello world!"));
}

does what I expect it to do. So what's wrong with it?

like image 443
n. 1.8e9-where's-my-share m. Avatar asked Aug 15 '26 08:08

n. 1.8e9-where's-my-share m.


2 Answers

To put it simply. This code:

var = foo();
var = bar();

is 100% equivalent to this in all1 situations:

foo();
var = bar();

Furthermore, if foo() has no side effects, it's 100% equivalent to just the last line:

// foo(); 
var = bar();

This goes for ANY function, including malloc. If we for a moment forget what malloc does and just focus on what just have been said, we can quickly realize what's written in the comments in this code:

answer = malloc(inputLength + 1);
// Here, the variable answer contains the return value from the call to malloc
answer = input;
// Here, it contains the value of input. The old value is overwritten, and
// is - unless you saved it in another variable - permanently lost.

What malloc does really simple. It returns a pointer to a memory block, or a NULL pointer if the allocation failed.2 That's it. What you are doing with a call like ptr = malloc(size) is absolutely nothing more fancy than storing that address in the pointer variable ptr. And pointer variables are in the same way no more fancy than other variables like int or float. An int stores an integer. A pointer stores a memory address. There's no magic here.

1It's 100% equivalent except you're doing really fancy stuff like reading the variable var with an external program 2malloc(0) can return a non-null pointer, but in practice it does not make a difference since it would be undefined behavior to dereference it, and allocating zero bytes is a pretty pointless (haha, point) operation.

like image 179
klutt Avatar answered Aug 18 '26 04:08

klutt


To answer this question, let's look at a somewhat simpler code fragment first.

int answer;

answer = 42;
answer = 0;

Even the most cursory of observers would notice that the first assignment

answer = 42;

is useless. It places the value of 42 into answer, only to be thrown away and replaced with 0 at the very next instant of time. So that line of code can be thrown away completely.

Let's verify this by looking at optimised assembly code generated by a C compiler. As we can see, the line answer = 42; does not indeed have any effect on the resulting machine code.

Now compare this to the code in question

answer = malloc(inputLength + 1);
answer = input;

If reasoning by analogy is valid in this case, then we must conclude that the first assignment is useless and can omitted. We place something (the result of malloc) in answer, only to be thrown away and replaced by something else a moment later.

Of course we cannot say whether it is applicable without further research, but we can confirm our suspicion by looking at the generated assembly again. And it is confirmed. The compiler does not even generate any calls to malloc and strlen! They are indeed useless.


So where does this intuition

It allocates the right amount of memory for the answer, and then copies the input to the answer

break down?

The problem lies in the eternal confusion between pointers and arrays.

One may often see claims that in C, arrays are pointers, or that pointers are arrays, or that arrays and pointers are interchangeable, or any number of variations thereof. These claims are all false and misleading. Pointers and arrays are completely different things. They often work together, but that's far cry from being one and the same. Let's break down pointers and arrays in the code example.

  • input is a pointer variable
  • input (presumably) points into a string, which is an array of char
  • answer is another pointer variable
  • malloc(...) dynamically allocates a new array of char and returns a pointer that points into said array
  • answer = malloc(...) copies that pointer to answer, now answer points into the array allocated by malloc
  • answer = input copies another pointer (that we have already seen above) into answer
  • now answer and input point into the same string, and the result of malloc is forgotten and thrown away

So this explains why your code is doing what you expect it to do. Instead of having two identical copies of the string "Hello world!" you have just one string and two different pointers into it. Which might seem like that's just what the doctor ordered, but it breaks down as soon as we do something ever so slightly complicated. For example, code like this

char *lineArray[MAX_LINES];
char buffer[BUF_LEN];
int i = 0;
while (i < MAX_LINES && fgets(buffer, BUF_LEN, stdin)) {
   lineArray[i++] = copyStr(buffer);
}

will end up with every element of stringArray pointing into the same string, instead of into a bunch of different lines taken from stdin.

OK, so now we have established that answer = input copies a pointer. But we want to copy an array, which we have just allocated space for! How do we do that?

Since our arrays are presumably NUL-terminated character strings, we can use a standard library function designed for copying NUL-terminated character strings.

strcpy(answer, input);

For other arrays we can use memcpy. The main difference is that we have to pass down the array length.

memcpy(answer, input, inputLength + 1);

Both variants will work in our case, but the first one is preferred because it reaffirms that we are dealing with strings. Here's the fixed copyStr for completeness:

char* copyStr(char* input) {
  int inputLength;
  char *answer;

  inputLength = strlen(input);

  answer = malloc(inputLength + 1);
  strcpy(answer, input);

  return answer;
}

Incidentally, it works almost the same as the non-standard but widely available strdup function (strdup has a better signature and working error checks, which we have omitted here).

like image 28
n. 1.8e9-where's-my-share m. Avatar answered Aug 18 '26 05:08

n. 1.8e9-where's-my-share m.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!