4

In C, what is the best way of prompting and storing a string without wasted space if we cannot prompt for the string length. For example, normally I would do something like the following...

char fname[30];
char lname[30];

printf("Type first name:\n");
scanf("%s", fname);

printf("Type last name:\n");
scanf("%s", lname); 

printf("Your name is: %s %s\n", fname, lname);

However, I'm annoyed with the fact that I have to use more space than needed so I do not want to use char fname[30], but instead dynamically allocate the size of the string. Any thoughts?

10
  • Use malloc to allocate a huge buffer. Other than your constant-storage spaces, malloced memory can be freed after use. Commented Aug 10, 2014 at 22:48
  • Asking user how long their name is wouldn't work so you're going to have to have an array big enough to hold the longest string allowed. Note that I say allowed because you should limit the input to prevent buffer overflow. Later, if you wish, you can resize the array to remove unused array elements. Commented Aug 10, 2014 at 22:54
  • 3
    If you have access to it, you could use getline(NULL, ... to allocate the buffer Commented Aug 10, 2014 at 22:56
  • What do you mean by "without wasted space"? Functions like getline will "waste" space at least in the sense of allocating more than absolutely necessary. Commented Aug 10, 2014 at 23:06
  • @mafso: that is precisely what the OP asks. Allocating a string in advance is tricky, because how long can a name be? Commented Aug 10, 2014 at 23:09

2 Answers 2

4

You can create a function that dynamically allocates memory for the input as the user types, using getchar() to read one character at a time.

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

void* safeRealloc(void* ptr, size_t size) {
  void *newPtr = realloc(ptr, size);
  if (newPtr == NULL) { // if out of memory
    free(ptr); // the memory block at ptr is not deallocated by realloc
  }
  return newPtr;
}

char* allocFromStdin(void) {
  int size = 32; // initial str size to store input
  char* str = malloc(size*sizeof(char));
  if (str == NULL) {
    return NULL; // out of memory
  }
  char c = '\0';
  int i = 0;
  do {
    c = getchar();
    if (c == '\r' || c == '\n') {
        c = '\0'; // end str if user hits <enter>
    }
    if (i == size) {
        size *= 2; // duplicate str size
        str = safeRealloc(str, size*sizeof(char)); // and reallocate it
        if (str == NULL) {
          return NULL; // out of memory
        }
    }
    str[i++] = c;
  } while (c != '\0');
  str = safeRealloc(str, i); // trim memory to the str content size
  return str;
}

int main(void) {
  puts("Type first name:\n");
  char* fname = allocFromStdin();

  puts("Type last name:\n");
  char* lname = allocFromStdin();

  printf("Your name is: %s %s\n", fname, lname);

  free(fname); // free memory afterwards
  free(lname); // for both pointers
  return 0;
}
Sign up to request clarification or add additional context in comments.

5 Comments

You should realloc at the end to remove all waste, right?
Yes, you should free() after using the data to remove the waste!
Make sure there is a terminating 0 at the end. I was thinking of this very same solution. Quite unexpectedly, it returns the correct string and length even if I used Backspace (on OSX Terminal). I would have guessed this needed special handling in the code.
@ericbn No, I mean realloc at the end of allocFromStdin to make the allocated buffer the perfect length to fit the name.
This actually has a memory leak because when realloc fails (returning NULL) it doesn't free the old the memory. Not a big deal since programs usually terminate when running out of memory, but worth pointing out. Also Fiddling Bits is correct, you should trim the size of the returned memory to the size of the content.
3

From man scanf:

• An optional 'm' character. This is used with string conversions (%s, %c, %[), and relieves the caller of the need to allocate a corresponding buffer to hold the input: instead, scanf() allocates a buffer of sufficient size, and assigns the address of this buffer to the corresponding pointer argument, which should be a pointer to a char * variable (this variable does not need to be initialized before the call). The caller should subsequently free(3) this buffer when it is no longer required.

this however is a POSIX extension (as noted by fiddling_bits).

To be portable I think that in your usage case I would prepare a function like the following:

char *alloc_answer() {
  char buf[1000];
  fgets(buf,sizeof(buf),stdin);
  size_t l = strlen(buf);
  if (buf[l-1]=='\n') buf[l]=0; // remove possible trailing '\n'
  return strdup(buf);
}

even if this solution will break lines longer than 1000 characters (but it prevents buffer overflow, at least).

A fully featured solution would need to read input in chunks and realloc the buffer on every chunk...

2 Comments

+1 Very clean solution. Suggestion: remove '\n' that fgets leaves in buf.
fgets reads at most one byte less than the second argument and still 0-terminates the buffer. fgets(buf, sizeof buf, stdin); is fine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.