1

I searched all the web but I haven't come to a real solution (maybe I haven't googled the right words, I don't know). but here we are: this is my first C project with structs, and I tried to use gets and puts to overcome the printf-scanf's problem with white spaces, but the gets led to a lot of problems (the program skipped directly to the puts, without the gets) so I come in a topic of another guest of this forum where was mentioned

scanf("%[^\n]",str);

I applied this to my code, but I get the same problem, and I have also tried

scanf("%[^\n]s",str);
scanf("%[^\n]",&str); //despite looked senseless to me

I had read about fgets and fscanf, but I'm on an earlier phase

Here is the code... I use Visual Studio 2015, running on Windows 10.

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

typedef struct s_libro {
    char titolo[50];
    char autore[50];
    char editore[20];
    int pagine;
    int costo$;
}t_libro;

int main() {
    t_libro libro1;
    t_libro libro2;
    printf("Inserire autore(max 50) ,titolo(max 50) , editore(max 20), pagine, costo$\n");
    //LIBRO 1
    printf("\nLIBRO 1\n");

    printf("\nInserisci il nome dell'autore del libro1 : \n");
    scanf("%[^\n]", libro1.autore);

    printf("\nInserire titolo del libro1 : \n");
    scanf("%[^\n]", libro1.titolo);

    printf("\nInserire nome dell'editore del libro1:\n");
    scanf("%[^\n]", libro1.editore);

    printf("\nInserire numero di pagine del libro1 : \n");
    scanf("%d", &libro1.pagine);

    printf("\nInserire il costo (dato numerico) del libro1 : \n");
    scanf("%d", &libro1.costo$);

    printf("\nLIBRO1\t");
    printf("\nTitolo Libro1\t  %s", libro1.titolo);
    printf("\nAutore Libro1\t  %s", libro1.autore);
    printf("\nPagine Libro1\t  %d", libro1.pagine);
    printf("\nEditore Libro1\t  %s", libro1.editore);
    printf("\nCosto Libro1\t  %d $", libro1.costo$);

    printf("\n\n");


    //LIBRO 2

    printf("\nLIBRO 2\n");

    printf("\nInserisci il nome dell'autore del libro2 : ");
    scanf("%[^\n]", &libro2.autore);

    printf("\nInserire il titolo del libro2 : ");
    scanf("%[^\n]", &libro2.titolo);

    printf("\nInserire L'editore del libro2:");
    scanf("%[^\n]", libro2.editore);

    printf("\nInserire il numero di pagine del libro2 : ");
    scanf("%d", &libro2.pagine);

    printf("\nInserire il costo (dato numerico) del libro2 : ");
    scanf("%d", &libro2.costo$);

    printf("\nLIBRO2\t");
    printf("\nTitolo Libro2\t  %s", libro2.titolo);
    printf("\nAutore Libro2\t  %s", libro2.autore);
    printf("\nPagine Libro2\t  %d", libro2.pagine);
    printf("\nEditore Libro2\t  %s", libro2.editore);
    printf("\nCosto Libro2\t  %d $", libro2.costo$);

    printf("\n\n");
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
redbite
  • 189
  • 1
  • 2
  • 10

2 Answers2

3

You have two problems:

  1. You don't check that scanf() succeeds.
  2. You don't allow for the fact that scanf() leaves the newlines in the input stream.

You have code such as:

printf("\nInserisci il nome dell'autore del libro1 : \n");
scanf("%[^\n]", libro1.autore);

printf("\nInserire titolo del libro1 : \n");
scanf("%[^\n]", libro1.titolo);

The first of these should work. The second will fail, because the first left a newline in the input stream, and the scan set doesn't recognize the newline as valid, so it fails.

There are several possible solutions. Which is best depends on your detailed requirements.

  1. Use standard C fgets() or perhaps POSIX getline() to read lines and then sscanf() to process the data.
  2. Use " %49[^\n]" in the format string to skip white space including newlines before reading up to 49 non-newlines into the variable. This is the minimal change. The length limitation prevents buffer overflows. Yes, the size in the format string doesn't include the null byte.
  3. Read the residue of the line after each input with scanf(). For example:

    static inline void gobble(void)
    {
        int c;
        while ((c = getchar()) != EOF && c != '\n')
            ;
    }
    

    and then:

    printf("\nInserisci il nome dell'autore del libro1 : \n");
    if (scanf("%49[^\n]", libro1.autore) != 1)
        …process error or EOF…
    gobble();
    
    printf("\nInserire titolo del libro1 : \n");
    if (scanf("%49[^\n]", libro1.titolo) != 1)
        …process error or EOF…
    gobble();
    

There are no doubt many other options; you will have to decide what works best for you. Note that you should check the status of scanf() each time you use it, and you should make sure that you do not allow overflows.


Incidentally, the scanf("%[^\n]", &str) variant is wrong. The type of the argument passed is char (*)[50] (if the variable is char str[50];), but the scan set conversion specification expects a char * — a quite different type. There's a quirk that means you get away with it; the address value of str and &str is the same, but the type is different, as could be noted by looking at (str + 1) and &str + 1 — the values will be quite different. The correct notation is scanf("%[^\n]", str), therefore (modulus adding overflow protection).

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
1

If you want to preserve white spaces, I'd suggest to use fgets and then remove any trailing new line character, e.g. using strcspn (as suggested by xing). Reading an item would then look like as follows:

printf("\nInserisci il nome dell'autore del libro1 : \n");
fgets(libro1.autore,50,stdin);
libro1.autore[strcspn(libro1.autore,"\n")] = '\0';

The advantage is that you are safe concerning buffer overflows if a user enters "to many characters"; Note that a valid name would consist of at most 48 characters (+ the new line character + string termination character). And - within regular inputs by the user - you are safe concerning new line characters that might influence any subsequent scanf("....")-statement.

Note that a scanf like scanf(" %[^\n]", libro1.autore) with the leading space in the format would not allow the user to enter an "empty" name, since the space in the format would consume the (only) new line character as a white space. A scanf without the leading space, i.e. scanf("%[^\n]", libro1.autore) would not consume any new line, leading to the problems you have.

Conclusio: don't use scanf in this scenario.

Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
  • 2
    While the use of `strcspn(libro1.autore,"\n")` is entirely correct to get the index of the `'\n'`, if any, to overwrite, if the `strlen` is needed later in the code it requires a duplicate call. In that case a `size_t len = strlen (librol.autore); and if (len && librol.autore[len - 1] == '\n') librol.autore[--len] = 0'` preserves and updates `len` for later use. No indication it is needed here, but something to keep in mind when dealing with that issue. – David C. Rankin Aug 06 '17 at 05:25