scanf() not working correctly within for loop

Viewed 182

I am attempting to store multiple strings from stdin into a temporary variable.

Each string will be entered following a new line character.

This is my attempt:

#include <stdio.h>
int main() {
    for (int i=0; i<10; i++) {
        char string;
        scanf("%s\n", &string);
        printf("%d\n", i);
    }
}

The for loop does not run as intended, it only loops twice, seems scanf is interfering with the loop.

stdin:

test
test

stdout:

7631717

Is there a way to store input/each line from stdin dynamically into a char variable without a fixed size/length?

3 Answers

to not be limited to a given size reading a string you can use the modifier 'm' :

char * p;

if (scanf("%ms", &p) == 1) {
  ...use p;
  free(p); /* when stop to be usefull
}

From the man of 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.

The previous way allows to red a 'word', to read a full line removing spaces (' ', newline, tab ...) at the beginning:

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

int main()
{
  char * p;

  if (scanf(" %m[^\n]", &p) == 1) {
    printf("'%s'\n", p);
    free(p);
  }
  else
    puts("error (EOF...)");
  
  return 0;
}

Compilation and execution:

/tmp % gcc -Wall c.c
/tmp % ./a.out
    aze qsd
'aze qsd'
/tmp % ./a.out < /dev/null
error (EOF...)
/tmp % 

If you do not have that modifier (not posix ...) to read a full line as it is you can do :

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

int main()
{
  char * p = malloc(1);
  size_t len = 0;
  int c;
  
  while (((c = getchar()) != '\n') && (c != EOF)) {
    p = realloc(p, len+1);
    if (p == NULL) {
      puts("not enough memory");
      return -1;
    }
    p[len++] = c;
  }
  p[len] = 0;
  
  printf("'%s'\n", p);
  free(p);
  
  return 0;
}

Compilation and execution :

/tmp % gcc -Wall c.c
/tmp % ./a.out
  aze qsd
'  aze qsd'
/tmp % ./a.out < /dev/null
''
/tmp % 

char string; is not a string but a single character. Reading in a string with format %s into a single character is undefined behaviour as soon as you enter at least one character. This is because the string termination character will be appended, too.

Write ...

char string[100];
scanf("%99s", string);

and everything should be fine.

BTW: If you intend to read in complete lines (including white spaces at the beginning, in between and at the end), consider using fgets instead of scanf.

Is there a way to store input/each line from stdin dynamically into a char variable without a fixed size/length?

If willing to going the POSIX route, use getline() to "store input/each line from stdin dynamically".

Lop off the potential trailing new-line if desired

       char *line = NULL;
       size_t len = 0;
       ssize_t nread;

       while ((nread = getline(&line, &len, stdin)) != -1) {
           if (nread > 0 && line[nread-1] == '\n') line[--nread] = 0;
           printf("Retrieved line of length %zu: <%s>\n", nread, line);
       }

       free(line);

I suspect nread > 0 && line[nread-1] == '\n' can be reduced to line[nread-1] == '\n' as I see not case where nread returns 0.

Related