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 */
}