0

I have a challenge: ask user to input first, the maximum number of characters, then, input the string. Memory should be dynamically allocated and the string inputted should be printed.

My code;

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        int size;
        char * pText = NULL;
        
        // get the maximum string length
        printf("\nEnter maximum length of string (a number): ");
        scanf("%d", &size);
        
        // allocate memory
        pText = (char *)malloc(size * sizeof(char));
    
        if(pText != NULL)
        {
            // get user's input string
            printf("\nEnter your string:\n");
            fgets(pText, size, stdin);
    
            printf("\nThe input string is %s.\n", pText);
        
        }
    
        free(pText);
    
        return 0;
    }

This is all the code that there is. The problem I am experiencing is in the if-block; the originally implemented code is,

    scanf(" ");
    gets(pText);

    printf("\nThe inputted string is %s.\n", *pText);

which is some wizardry that I believe removes terminating characters from scanf, enabling gets to read in a string along with terminating character (if I have the information correct); it has not been fully explained. While that implementation works, I have been told that it is possible to use fgets to implement the same functionality.

My attempt to implement fgets leads to the outcome where the user cannot input a string because the program terminates after the maximum number of characters (as an integer) has been entered and <return> has been pressed. I believe that because pText IS NULL, or zero, the program terminates before any text can be written.

I have tried reading around the subject of fgets, but most examples deal with accepting data from an input file, not stdin. I'm not sure what the problem is, or where I am going wrong, but I would appreciate some enlightenment.

Den
  • 173
  • 11
  • 3
    Remember that strings are really named ***null-terminated** strings*. That means a string of size `X` needs `X + 1` elements to fit the terminator. Also note that `fgets` will add the ending newline if it can fit (which `gets` will not do). – Some programmer dude Mar 01 '21 at 12:34
  • 1
    And never ***ever*** use `gets`. It's so [dangerous](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-so-dangerous-that-it-should-not-be-used) that it has been removed from the C standard. – Some programmer dude Mar 01 '21 at 12:35
  • Before you continue, "pointers are arrays"? No that's not true. A pointer can point to the first element of an array, but it's not an array in itself. And arrays can *decay* to a pointer to its first element, but an array isn't a pointer in itself. – Some programmer dude Mar 01 '21 at 12:47
  • As for your question, "it does not work" is just not enough of a problem description. ***How*** doesn't it work? What happens? What is supposed to happen? For some specified input what is the expected and actual output? Please take some time to refresh [ask] and [this checklist](https://meta.stackoverflow.com/questions/260648/stack-overflow-question-checklist). – Some programmer dude Mar 01 '21 at 12:49
  • 1
    `Enter maximum number of characters: 8abcdefgh` – Cheatah Mar 01 '21 at 12:50
  • But that doesn't match the code you show? In the code you show you will never call `fgets` with a null-pointer `pText`. Are you asking why you can't pass a null pointer to `fgets`, with some code you don't show us? Or are you asking about some other problem with the code you actually show? – Some programmer dude Mar 01 '21 at 12:58
  • @user3121023 That loop could become infinite if there's an error or if the user presses the "end of file" key instead. – Some programmer dude Mar 01 '21 at 13:43
  • Don't forget that [`scanf()` leaves the newline char in the buffer](https://stackoverflow.com/questions/5240789/scanf-leaves-the-new-line-char-in-the-buffer) so the first string read will be "empty". – Weather Vane Mar 01 '21 at 15:21
  • You shouldn't pass a null pointer to `fgets`. It doesn't have null-pointer checks and will happily dereference the pointer to store the data it reads without concern of pointer validity. If you get build-error then please create a proper [mcve] replicating the errors, and show it to us together with a full and complete copy-paste of the errors you get. I suggest you post it as a new question and delete this one (unless this have helped you in other ways). `fgets` is almost a drop-in replacement for `gets`, but with additional arguments making it safer. – Some programmer dude Mar 01 '21 at 16:43
  • @Someprogrammerdude Perhaps I started my query completely on the wrong foot; I am learning C via an online course. I am not a developer (yet). I am trying to understand abstruse explanations within the course (or none at all and having to guess, or work hard to find answers). I am happy to receive comments, but I am at fault for commentors' assumption of my level of proficiency; forgive me. – Den Mar 01 '21 at 16:53
  • My advice for you is to invest in some books. Even rather bad books are typically better than a mediocre online-tutorial. And of course taking classes with teachers and other students is even better, as it allows better interaction with your teacher and a more tight-knit group of like-minded students. But books is really a must in my opinion. And the books doesn't have to be brand new, anything written in the last 15-20 years should suffice (as long as it includes the C99 standard). – Some programmer dude Mar 01 '21 at 17:14

1 Answers1

2

Your problem is that you are mixing scanf and fgets in the same program. It is possible, but only to cautious programmers because scanf stops reading as soon as it could complete its conversion or find an error.

So when your user type 10Return, scanf reads the 1 and 0 characters (to form 10) and leaves a '\n' character in the input buffer. On next fgets, you immediately get an empty line and normally exit the program saying that nothing was read.

You should at least clean the input buffer up to the end of line:

scanf("%d", &size);
while (((int c = fgetc(stdin)) != '\n') && (c != EOF));  // read up to the end of line

That should be enough to fix the read problem, but you should considere controlling the return value or scanf and the validity of size (what for xyz or -1 as input?).

Then if you want fgets to correctly return a line up to size useful characters, you should give it a buffer of size + 2 characters, with a place for the newline and the terminating null (BTW sizeof(char) is 1 per standard).

// allocate memory
pText = (char *)malloc(size + 2);     // do not forget the new line and the null

Finally, you can get rid of the newline with strcspn

    fgets(pText, size, stdin);
    pText[strcspn(pText, "\r\n")] = '\0';  // remove the optional end of line
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Thank you for your help. As the challenge is an exercise to fimiliarise one with ```malloc``` and pointers, I appreciate your walk-through of issues and solutions. There are, of course, functions that you have introduced, which are new to me. ```fgetc``` being one; could I have implemented something like, ```while((c = getchar()) != '\n' && (c != EOF));```? Also new to me is ```strcspn```; I understand how it functions, but I'm not sure I completely understand how you have implemented it. Nonetheless, and again, thank you. – Den Mar 01 '21 at 16:28
  • @Den: In fact, `getchar()` is `fgetc(stdin)`. That means that `fgetc` extracts one character from a stream, while `getchar` extracts it from `stdin`. `strcspn` is one of the functions from the C Standard Library, declared in `string.h`. When unsure of what a standard function does, you can always consult the excellent [cppreference](https://en.cppreference.com/) – Serge Ballesta Mar 01 '21 at 16:39
  • Thank you for confirming the substitute code works just as well, and thank you for the link to the resource - now bookmarked. – Den Mar 01 '21 at 16:58