How to input a string with unknown size

Viewed 4576

I'm a bit confused about strings in C. I understand that declaring buffer size is important since otherwise, it can cause buffer overflow. But I need to know how do I take a string input that I don't know the size of. For instance, if I wanted to take a line of text from the user as input and I had no way of knowing how long their text would be, how do I do it?

I've tried dynamically allocating memory as the user gives an input. Here's the code-

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

int main()
{
    char *str, ch;
    int size = 10, len = 0;
    str = realloc(NULL, sizeof(char)*size);
    if (!str)return str;
    while (EOF != scanf_s("%c", &ch) && ch != '\n')
    {
        str[len++] = ch;
        if (len == size)
        {
            str = realloc(str, sizeof(char)*(size += 10));
            if (!str)return str;
        }
    }
    str[len] = '\0';
    printf("%s\n", str);
    free(str);
}

The problem is, when I compile it using VS-2017, I get these errors-

source.c(10): warning C4473: 'scanf_s' : not enough arguments passed for format string

source.c(10): note: placeholders and their parameters expect 2 variadic arguments, but 1 were provided

source.c(10): note: the missing variadic argument 2 is required by format string '%c'

source.c(10): note: this argument is used as a buffer size

I think that dynamically allocating memory as I go on(like in the above code) should work, but I'm probably doing something wrong. Is there a way to make this work?

EDIT: Word.

5 Answers
  1. You should use getchar not scanf_s
  2. You should use int ch; not char ch; for EOF

The following code could work:

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

int main() {
    char *str = NULL;
    int ch;
    size_t size = 0, len = 0;

    while ((ch=getchar()) != EOF && ch != '\n') {
        if (len + 1 >= size)
        {
            size = size * 2 + 1;
            str = realloc(str, sizeof(char)*size);
        }
        str[len++] = ch;
    }
    if (str != NULL) {
        str[len] = '\0';
        printf("%s\n", str);
        free(str);
    }

    return 0;
}
  • scanf_s requires a buffer size as argument, and you don't want that
  • Generally scanf will read from stdin once the \n is fed anyways.
  • Using getchar here is a better approach

Here is a version using getchar:

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

int     main()
{
  char  *str = NULL;
  int   ch;
  int   capacity = 10, size = 0;

  str = realloc(str, sizeof(*str) * (capacity + 1));
  if (!str) return 1;
  while ((ch = getchar()) != EOF && ch != '\n')
    {
      if (size == capacity)
      {
        capacity += 10;
        str = realloc(str, sizeof(*str) * (capacity + 1));
        if (!str) return 1;
      }
      str[size] = (char)ch;
      size++;
    }
  str[size] = '\0';
  printf("%s\n", str);
  free(str);
  return 0;
}

scanf_s requires an additional "buffer-size" parameter for each formatted input place holder. That's what your compiler complains. So when using scanf_s, you'd have to write scanf_s("%c", &ch, 1). But you could simply use scanf as well, as you can guarantee that your buffer ch is large enough to take on a character value in any case. So scanf("%c",&ch) will work securely as well.

Note further that scanf (and scanf_s) return the number of values correctly read in; A return value of 0 would be possible as well, also indicating that nothing was read in.

So I'd test it as ...

while (scanf("%c", &ch)==1 && ch != '\n') 

Your code is almost fine.

Two things should be changed:

  1. don't use scanf but use getc
  2. ch should be int, otherwise the comparision with EOF will cause trouble.
  3. Nitpicking detail: main should return 0 in case of success and a value different from 0 in case of failure.

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

int main()
{
  char *str;
  int ch;
  int size = 10, len = 0;

  str = realloc(NULL, sizeof(char)*size);
  if (str == NULL)
    return 1;

  while ( (ch = getc(stdin)) && ch != EOF && ch != '\n')
  {
    str[len++] = (char)ch;

    if (len == size)
    {
      str = realloc(str, sizeof(char)*(size += 10));

      if (str == NULL)
        return 1;
    }
  }

  str[len] = '\0';
  printf("%s\n", str);
  free(str);
}

The problem is, when I compile it using VS-2017, I get these errors-

source.c(10): warning C4473: 'scanf_s' : not enough arguments passed for format string

source.c(10): note: placeholders and their parameters expect 2 variadic arguments, but 1 were provided

source.c(10): note: the missing variadic argument 2 is required by format string '%c'

source.c(10): note: this argument is used as a buffer size

The warning and notes are quite explicit, aren't they?

Looking up the related documentation reveals under the "Remarks" section:

Unlike scanf and wscanf, scanf_s and wscanf_s require the buffer size to be specified for all input parameters of type c, C, s, S, or string control sets that are enclosed in []. The buffer size in characters is passed as an additional parameter immediately following the pointer to the buffer or variable.

So you want to change

  while (EOF != scanf_s("%c", &ch) && ch != '\n')

to look at least like this:

  while (EOF != scanf_s("%c", &ch, sizeof ch) && ch != '\n')

even better would be to provide complete error checking like this:

  {
    int result;
    while (1 == (result = scanf_s("%c", &ch, sizeof ch)) 
           && ch != '\n')
    {
      ...
    }

    if (EOF == result && ferror(stdin)) /* As scanf'ing uses stdin, 
                                           test its error status. */
    {
      fprintf(stderr, "scanf_s() failed.\n")
    }
  }
Related