2

I am creating a char array (representing a string) and adding characters to the array from the end of the array. But I don't know how many characters I will be adding in total ahead of time.

Once I have finished adding all the characters I need, I want to advance the pointer so that it points to the start of the string. But I am unable to free the pointer after doing so.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

char* getGreeting() {
  // Allocate a 0-initialized buffer that fits 10 characters
  char* res = (char*) calloc(10, sizeof(char));

  // Write some characters into the array from the end
  res[4] = 'H';
  res[5] = 'e';
  res[6] = 'l';
  res[7] = 'l';
  res[8] = 'o';
  res[9] = '\0';

  // Move the pointer along until it gets to the first non-null character
  while (*res == '\0') {
    res++;
  }
  
  // Printing out each character to see what's going on
  int i = 0;
  while (res[i] != '\0') {
    printf("c[%d]: %c\n", i, res[i]);
    i++;
  }
  printf("c[%d]: \\0\n", i);

  // Print out the string to see what's going on
  printf("res: %s\n", res);

  // Return the pointer that may have been advanced
  return res;
}

int main() {
  char* greeting = getGreeting();

  // Trying to free the memory pointed to by the pointer
  free(greeting);
  return 0;
}

This is the printed output:

c[0]: H
c[1]: e
c[2]: l
c[3]: l
c[4]: o
c[5]: \0
res: Hello
free(): invalid pointer

How can I create a string that I don't know the length of beforehand? Or, alternatively, how do I correctly shorten a string that was populated from the end of the char array?


The problem I was originally trying to solve

I am learning C and doing some coding challenges to get familiar with the language. This particular challenge came from having to add two large numbers that are given as strings (represented as decimal). I add the right-most digits, store the unit-digit to the end of the char array, carry the tens-digit over, and repeat with the next digit. That's why I populate the string from the end.

dayuloli
  • 16,205
  • 16
  • 71
  • 126
  • `while (*res == '\0') {` risks going too far if the _string_ formed by `Write some characters into the array from the end` was only `res[9] = '\0';`. Recall a valid _string_ may only be `""`. Better to keep track of the count of characters added at the end than to zero fill the entire array and look for beginning. – chux - Reinstate Monica Jul 08 '23 at 06:46
  • 1
    Consider posting the use case that needs "adding characters to the array from the end" and with that higher level goal known, an even better solution may be made. – chux - Reinstate Monica Jul 08 '23 at 06:47
  • You have to *also* save the original pointer, and use that in `free`. – BoP Jul 08 '23 at 08:36
  • This seems like an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Filling an allocation from the end to create a string is a solution to some problem: what is that problem? If the problem is to create a string with size known only at runtime using minimal memory, the usual approach is to allocate a reasonable amount of memory and fill it with characters, growing as needed with `realloc`, and finally trimming to exact size with `realloc`. – ad absurdum Jul 08 '23 at 14:27
  • I am learning C and doing some coding challenges to get familiar with the language. This particular challenge came from having to add two large numbers that are given as strings (represented as decimal). I add the right-most digits, store the unit-digit to the end of the char array, carry the tens-digit over, and repeat with the next digit. I haven't came across/used `memmove` and `realloc` yet. Currently, I only know how to `malloc()`/`calloc()`/`free()` and how to advance the pointer. – dayuloli Jul 08 '23 at 16:32
  • Thanks @adabsurdum I will try your suggestion for future challenges. – dayuloli Jul 08 '23 at 16:33
  • You might consider building the result string from the left and reversing when complete. – ad absurdum Jul 08 '23 at 16:39
  • "I am learning C" I highly recommend to *not* try to learn C or programming in general from random Internet tutorials and coding challenges. Get a good book instead. Do the challenges if you want but only after a thorough reading of relevant chapters in the book. Coding challenges are for the most part math puzzles in disguise. They don't really teach you much about programming. – n. m. could be an AI Jul 08 '23 at 17:06

2 Answers2

2

For free the standard (draft n1570) states in 7.22.3.3:

... if the argument does not match a pointer earlier returned by a memory management function, ....., the behavior is undefined

"memory management function" refers to malloc and friends.

So the answer is that you can't do what you are trying. Changing res and later use of the changed value in a free call is illegal. In other words - never change the value of res.

The best way to handle the problem is to move the part of the string you want to keep to the front of the allocated memory.

memmove(res, res + OFFSET, NUMBER_OF_CHARS_TO_MOVE);

memmove will be much be faster than a second allocation call, a copy and a free call.

Remember that NUMBER_OF_CHARS_TO_MOVE must include the termination character. If the number isn't already available from the previous code, it can be found like strlen(res + OFFSET)+1

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
-1

First of all, to quote this answer:

You're trying to free the inside of a block of memory. When you have in fact allocated a block of memory, you can only free it from the pointer returned by malloc. That is to say, only from the beginning of the block. You can't free a portion of the block from the inside.

Once you have advanced a pointer from its beginning, free cannot free the memory anymore. This is because most implementations store some meta-information about the size of the block in the address(es) "before" the address pointed to by the pointer returned from malloc or calloc. If you advance the pointer and return it, free don't see that metadata "before" this new address, and don't know how much memory it needs/can free. See the answers to How does free know how much to free? for more details/discussion.

What you need to do is to allocate a new block of memory once you know the correct size, copy the characters from the old char array into the new char array, free the original block of memory, and then return the pointer to the new block of memory.


#include <stdlib.h>
#include <string.h>
#include <stdio.h>

char* getGreeting() {
  int size = 10;
  char* temp = (char*) calloc(size, sizeof(char));
  char* originalTemp = temp; // Keep hold of the address of the start of the block
  temp[4] = 'H';
  temp[5] = 'e';
  temp[6] = 'l';
  temp[7] = 'l';
  temp[8] = 'o';
  temp[9] = '\0';
  while (*temp == '\0') {
    temp++;
    size--;
  }
  
  char* res = (char*) calloc(size, sizeof(char)); // Create a new block of memory of the right size
  int i = 0;
  while (*temp != '\0') {
    res[i] = *temp;
    temp++;
    i++;
  }
  free(originalTemp); // Free the original block
  return res;
}

int main() {
    char* greeting = getGreeting();
    
    // Print out the string to see what's going on
    printf("greeting: %s\n", greeting);
    
    free(greeting);
    return 0;
}
dayuloli
  • 16,205
  • 16
  • 71
  • 126