7

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.

2
  • Don't use scanf here but rather getc. Commented Nov 2, 2018 at 9:55
  • 1
    And don't use scanf_s() or any of the other *_s() functions from Annex K of the C standard. They're no more secure than standard C functions if the standard ones are used properly, and the *_s() functions are not portable as implemented by Microsoft: "As a result of the numerous deviations from the specification the Microsoft implementation cannot be considered conforming or portable." Commented Nov 2, 2018 at 10:17

5 Answers 5

4
  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;
}
Sign up to request clarification or add additional context in comments.

2 Comments

size and len should be size_t not int. Also realloc() might fail.
@Yunbin Liu can you please tell me why are we checking len+1 >= size and why we are updating size = size *2 + 1. Please do tell me I am unable to understand that part
2
  • 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;
}

3 Comments

Being picky: sizeof(char) equals 1 by definition, so just drop it. Or if you intend to provide code being robust against changes of str's type then use sizeof *str instead.
First call to realloc(str) might be problematic since str is never initialized. Also, size == capacity is not good enough since you need an additional space for the ending nul-character.
Yeah, sorry, I missed that part, although it will still fail for an empty string, i.e. when you never enter the loop.
1

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') 

Comments

1

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);
}

Comments

1

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")
    }
  }

Comments

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.