2

So I have a code, which gets in input arbitrary number of lines of arbitrary length. Each line generally contains one or more words separated by whitespace. I have to output that strings.

So this my code. I am newbie in C, and don't be so rude for my dumb-code ;)

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

int main () {
    int i = 1;
    char **list = malloc(1000 * sizeof(char*));
    while(!feof(stdin)){
        list[i] = malloc(1000 * sizeof(char));
        printf("Enter string with num %d: ", i);
        scanf("%99[^\n]", list[i]);
        i++;
    }
    printf("\n");
    for (int j = 1; j < i; j++)
        printf("Your strings by num %d: %s\n", j, list[j]);

    return 0;
}

But I have problem with scanf, and I can't contiune my inputting after whitespace. I think my problem is

scanf("%99[^\n]", list[i]);

In addition to that, I can't use the gets(), getchar() families, and also specifications %c and %m.

CyberDemon
  • 414
  • 1
  • 10
  • 17
  • 3
    You should read through [Why is “while ( !feof (file) )” always wrong?](https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong) – Retired Ninja Oct 16 '21 at 16:36
  • Does this answer your question? [How to take Line input in C language with whitespace](https://stackoverflow.com/questions/68792715/how-to-take-line-input-in-c-language-with-whitespace) – Retired Ninja Oct 16 '21 at 16:39
  • I can't use gets, because it is task – CyberDemon Oct 16 '21 at 16:41
  • Always check the value returned by `scanf`, which, BTW may be `EOF`. – Bob__ Oct 16 '21 at 16:46
  • @CyberDemon You should decide if your line length is max. 99, max. 999, or truly arbitrary. Given your current format string of `%99[^\n]`, you could get away with `list[i] = malloc(100)`. – Steve Summit Oct 16 '21 at 18:24
  • Better `fgets (list[i], 1000, stdin)` followed by `list[i][strcspn(list[i], "\n")] = 0;` to trim the `'\n'` from the end... and you **NEVER** ever want to use `gets()` -- which is so prone to exploit by buffer overrun it has been removed from the C-library in C11. – David C. Rankin Oct 16 '21 at 18:44
  • Year, I can't use gets(), because it is restriction from my teacher. And sorry for being rude) – CyberDemon Oct 16 '21 at 18:57
  • @CyberDemon - if this lesson is about `scanf()` that is fine -- it is one of the, if not the, most misused input function by new C programmers. It has a number of pitfalls that unless you know the manual (man page) cold, are likely to bite you. Spend an hour with [man 3 scanf](https://man7.org/linux/man-pages/man3/scanf.3.html) and it will save you 100 hours of grief. That said, if you are not required to use `scanf()` then generally all user-input should be taken with `fgets()` using a buffer of sufficient size (don't skimp 1024 is fine). Trim the `'\n'` with `buffer[strcspn(buffer,"\n")]=0;` – David C. Rankin Oct 16 '21 at 19:41
  • To get the length and trim the `'\n'` in one-shot, `size_t len; ... buffer[(len = strcspn(buffer, "\n"))] = 0;`. Put that in your hip-pocket for later. Also in your case here. Since user-input takes a second or two, you can read the input into a fixed buffer, and only allocate a pointer and storage for the input after validating the input is good. You simply call `realloc()` to allocate one more pointer and then allocate for length (+1) characters needed. This avoid pre-allocating all 1000 pointers up front. Reading all input at once from a file, then pre-allocation makes sense. – David C. Rankin Oct 16 '21 at 19:44

2 Answers2

2

with %99[^\n] you scan till you encounter \n. So first line is read without hitch. But as the \n has not been scanned, it remains in the unread part of stdin. The next time you scan, you encounter '\n' again and you have actually scanned an empty string. So use %99[^\n]%*c the %*c part means you ignore a character.

  • In the question, OP stated that they are not allowed to use the `%c` format specifier. This is probably a constraint on their homework assignment. However, I am still upvoting the answer, because the constraints do not make sense, unless OP is expected to use `%s` instead of `%[set]`. – Andreas Wenzel Oct 16 '21 at 17:07
  • Consider proposing `" %999[^n]"` (note the leading space) instead, as mentioned in this [comment](https://stackoverflow.com/questions/68792715/how-to-take-line-input-in-c-language-with-whitespace#comment121576084_68792715) to the proposed dupe. – Bob__ Oct 16 '21 at 19:36
  • Technically the `'*'` is the "*assignment-suppression character*" which when paired with a *conversion specifier* causes `scanf()` to read and discard the characters matching the specifier. – David C. Rankin Oct 16 '21 at 19:36
1

You have done one of the No. 1 things that is guaranteed to cause problems with your logic. You are mixing i as 1-based (for counting), but should be 0-based for indexing. In C all arrays and storage are Zero-based. The first element is element 0. When you start mixing a 1-based counter with 0-based indexing, you complicate your logic and must account for the shift in each instance the variable is used as a counter or as an index -- try and avoid this at all costs.

Instead of initializing int i = 1; initialize as int i = 0; you are using it as an index. When you need to output as a counter simply use i + 1. In your case this means you are allocating for 1000 pointers but you never use the first one. (you can do that -- but it is just sloppy) Always index from 0 and adjust the output numbering as needed by adding to the index. (also rather than just using i to track the number of pointers allocated, just a more descriptive variable name, e.g. n_pointers or nptrs)

As mentioned in the comment, since you are taking user-input from the keyboard, there is no benefit in pre-allocating for 1000 pointers at the beginning -- there is no program efficiency gained. In the time it takes a user to type "cat" you can have reallocated a new pointer and allocated for storage 1000 times over without introducing any delay.

A better approach here is to simple declare a fixed buffer (character array) to use for all input and then after you validate that good input was received, just realloc() a new pointer and allocate storage for the input. That way you do not over-allocate pointers.

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

#define MAXC 1000       /* if you need a constant, #define one (or more) */

int main () {
    
    size_t nptrs = 0;       /* counter for no. pointers allocated */
    char **list = NULL,     /* list and fixed buffer for read */
         buf[MAXC];
    ...

Since you are apparently stuck using scanf(), as mentioned in the comments, spend an hour with the man page -- it will save you 100 hours of grief later. scanf() is full of pitfalls for the new C programmer, like knowing which conversion specifiers discard leading whitespace and which don't and ... 50 more pitfalls just like it.

Here you want to read whitespace separated words with scanf() so you are going to use the %[...] conversion specifier to read all characters not-including the '\n' character. (lesson, %[...], %c and %n are the conversions that do NOT discard leading whitespace). So in your case, to discard leading whitespace, you will need to include a space before " %[^\n]".

You will also need to know the number of characters consumed so you can check against your 999 field-width limit to know whether the line you just tried to read is too long to fit. You can use the pseudo-conversion %n to get the number of characters consumed up to the point where it appears in your format string.

Since your read with not include the '\n' character at the end, if your buffer is filled 999 characters (plus the nul-terminating character), additional characters in that line remain unread which you will need to read and discard so your next read will start with the next line.

(you would normally do that looping with getchar() until the '\n' is found, but since you can't use it, you will use scanf() again but WITHOUT discarding leading whitespace)

Whenever you are looping collecting user-input (especially when specific input like an integer value is required), you will use a continual loop around your input routine to require the user to provide the needed type of input as well as any input within a specific value range. When you receive and validate the needed input was provided, you simply break your read-loop. Otherwise, you simply loop again until you get what you need. The key to validating any user-input is to check the return (and the value within limits if needed).

Putting the approach to reading into a fixed buffer, validating, and discarding characters if the line is too long, you could do:

    ...
    puts ("input\n");
    while (1) { /* loop continually until manual EOF */
        int nchar, rtn;     /* no. of chars consumed, scanf return */
        
        printf ("Enter string with num %zu: ", nptrs + 1);  /* prompt */
        rtn = scanf (" %999[^\n]%n", buf, &nchar);          /* save return */
        if (rtn == EOF) {                                   /* check EOF */
            puts ("EOF\n\noutput\n");
            break;
        }
        if (nchar == 999) { /* check line-too-long */
            fputs ("errro: line exceeds MAXC chars.\n", stderr);
            while (nchar == 999) {  /* loop discarding remainder of line */
                if (scanf ("%999[^\n]%n", buf, &nchar) != 1) {
                    goto getnextline;   /* skip over allocation/storage */
                }
            }
        }
        ...

Now reallocating an additional pointer and storage for each input and copying from the fixed buffer to allocated storage, you could do:

        ...
        /* always realloc using temp pointer or risk memory leak */
        void *tmp = realloc (list, (nptrs + 1) * sizeof *list);
        if (!tmp) {
            perror ("realloc-list");
            break;
        }
        list = tmp;         /* assign realloc'ed block of ptrs to list */
        
        list[nptrs] = malloc (nchar + 1);           /* allocate for string */
        memcpy (list[nptrs], buf, nchar + 1);       /* don's scan for \n */
        
        nptrs += 1;         /* increment pointer count */
        getnextline:;
    }
    ...

All that remains is outputting your lines (and shifting your index to counter to match your input prompt). Since you are ending your program, don't forget to free() the storage for each line and then free the pointers at the end. Yes, that will happen when the program exits, but you will not always be allocating in main() and failure to free the memory you allocate in a function will create a memory leak in your program. So build good habits early. Always track your memory allocations and free() the memory when it is no longer needed, e.g.

    ...
    for (size_t j = 0; j < nptrs; j++) {
        printf("Your strings by num %zu: %s\n", j + 1, list[j]);
        free (list[j]);     /* free storage for string */
    }
    free (list);            /* don't forget to free pointers */
}

That's it. That will take all of the input needed, eliminate any limit on the number of lines that can be entered (up to the limit of your virtual memory), output all stored lines and free all memory allocated before exiting.

Example Use/Output*

$  ./bin/scanflines
input

Enter string with num 1: string with   1
Enter string with num 2:       string with   2
Enter string with num 3:      string with   3
Enter string with num 4:     string with   4
Enter string with num 5:    string with   5
Enter string with num 6:   string with   6
Enter string with num 7:  string with   7
Enter string with num 8: string with   8
Enter string with num 9: EOF

output

Your strings by num 1: string with   1
Your strings by num 2: string with   2
Your strings by num 3: string with   3
Your strings by num 4: string with   4
Your strings by num 5: string with   5
Your strings by num 6: string with   6
Your strings by num 7: string with   7
Your strings by num 8: string with   8

Memory Use/Error Check

In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

It is imperative that you use a memory error checking program to ensure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.

For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.

$ valgrind ./bin/scanflines
==7141== Memcheck, a memory error detector
==7141== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==7141== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==7141== Command: ./bin/scanflines
==7141==
input

Enter string with num 1: string with   1
Enter string with num 2:       string with   2
Enter string with num 3:      string with   3
Enter string with num 4:     string with   4
Enter string with num 5:    string with   5
Enter string with num 6:   string with   6
Enter string with num 7:  string with   7
Enter string with num 8: string with   8
Enter string with num 9: EOF

output

Your strings by num 1: string with   1
Your strings by num 2: string with   2
Your strings by num 3: string with   3
Your strings by num 4: string with   4
Your strings by num 5: string with   5
Your strings by num 6: string with   6
Your strings by num 7: string with   7
Your strings by num 8: string with   8
==7141==
==7141== HEAP SUMMARY:
==7141==     in use at exit: 0 bytes in 0 blocks
==7141==   total heap usage: 18 allocs, 18 frees, 2,492 bytes allocated
==7141==
==7141== All heap blocks were freed -- no leaks are possible
==7141==
==7141== For counts of detected and suppressed errors, rerun with: -v
==7141== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Always confirm that you have freed all memory you have allocated and that there are no memory errors.

There is a lot here to digest, so take your time and make sure you understand why each line was written the way it was and what it is doing. If you have questions, drop a comment below.

In closing, know that in this case fgets() into buffer would have been a far better and recommended approach over using scanf(). But since it appears that is off limits, it can be done with scanf(), and is a good learning exercise -- but absent the learning exercise, fgets() is the much better choice.

Complete Program

For convenience:

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

#define MAXC 1000       /* if you need a constant, #define one (or more) */

int main () {
    
    size_t nptrs = 0;       /* counter for no. pointers allocated */
    char **list = NULL,     /* list and fixed buffer for read */
         buf[MAXC];
    
    puts ("input\n");
    while (1) { /* loop continually until manual EOF */
        int nchar, rtn;     /* no. of chars consumed, scanf return */
        
        printf ("Enter string with num %zu: ", nptrs + 1);  /* prompt */
        rtn = scanf (" %999[^\n]%n", buf, &nchar);          /* save return */
        if (rtn == EOF) {                                   /* check EOF */
            puts ("EOF\n\noutput\n");
            break;
        }
        if (nchar == 999) { /* check line-too-long */
            fputs ("errro: line exceeds MAXC chars.\n", stderr);
            while (nchar == 999) {  /* loop discarding remainder of line */
                if (scanf ("%999[^\n]%n", buf, &nchar) != 1) {
                    goto getnextline;   /* skip over allocation/storage */
                }
            }
        }
        /* always realloc using temp pointer or risk memory leak */
        void *tmp = realloc (list, (nptrs + 1) * sizeof *list);
        if (!tmp) {
            perror ("realloc-list");
            break;
        }
        list = tmp;         /* assign realloc'ed block of ptrs to list */
        
        list[nptrs] = malloc (nchar + 1);           /* allocate for string */
        memcpy (list[nptrs], buf, nchar + 1);       /* don's scan for \n */
        
        nptrs += 1;         /* increment pointer count */
        getnextline:;
    }
    
    for (size_t j = 0; j < nptrs; j++) {
        printf("Your strings by num %zu: %s\n", j + 1, list[j]);
        free (list[j]);     /* free storage for string */
    }
    free (list);            /* don't forget to free pointers */
}
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Whoever dowvoted this answer, kindly have the **integrity** to explain why? There is not a single incorrect part of the answer. – David C. Rankin Oct 17 '21 at 05:13