Originally Posted by
Mr.Lnx
the correct version of read_line function I think is
Well, there is a slight issue: it does not consider premature end of input (EOF), only a newline. Using while((ch=getchar()) != EOF && ch != '\n' && i<n) would fix that.
Plus, I prefer getline() semantics. One way to write it yourself, with small changes, is
Code:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
size_t read_line(char **const lineptr, size_t *const sizeptr, FILE *const in)
{
char *line;
size_t size, have;
int c;
if (!lineptr || !sizeptr || !in) {
errno = EINVAL; /* Invalid parameters! */
return 0;
}
if (*lineptr) {
line = *lineptr;
size = *sizeptr;
} else {
line = NULL;
size = 0;
}
have = 0;
while (1) {
if (have + 2 >= size) {
/* Grow size by a bit more, to avoid calling realloc too often.
* This one grows size to the next multiple of 128 from have.
* If you don't like it, just use
* size = have + 256;
* or similar, that'll work just as well.
* Note that realloc(NULL, size) == malloc(size).
*/
size = (have | 127) + 129;
line = realloc(line, size);
if (!line) {
errno = ENOMEM;
return (size_t)0;
}
*lineptr = line;
*sizeptr = size;
}
c = getc(in);
if (c == EOF || c == '\n')
break;
/* Note: We do not store newline in line. */
/* Skip control characters, except whitespace. */
if (iscntrl(c) && !isspace(c))
continue;
line[have++] = c;
}
/* I/O error? Those are rare, but possible. */
if (ferror(in)) {
errno = EIO;
return 0;
}
/* Because we made sure we had room for
* at least two characters, and one might have
* taken by c, we still are guaranteed to have
* room for the string-terminating '\0'. */
line[have] = '\0';
/* We always set errno, unlike getline(). */
errno = 0;
return have;
}
This has no line length limitations, and is very easy to use:
Code:
char *line = NULL;
size_t size = 0;
size_t len;
while (1) {
len = read_line(&line, &size, stdin);
if (errno) {
fprintf(stderr, "Error reading from standard input: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* End of input? Note, we tried to read first. */
if (len == 0 && feof(stdin))
break;
/* We have 'len' characters in line, no newline,
* and no non-whitespace control characters
* such as embedded '\0'. Do something with line,
* but do not modify the pointer itself. */
}
/* Line buffer is no longer needed, so free it. */
free(line); /* Note, free(NULL) is safe. */
line = NULL;
size = 0;
In your use case, I'd read each user day and reminder as a line, then just parse the day out of the line using sscanf():
Code:
char *curr = line;
int day, n;
n = -1; (void)sscanf(curr, " %d %n", &day, &n);
if (n > 0) {
curr += n;
have -= n;
/* Day is in 'day', 'curr' has the rest of the line,
* and 'have' is updated to the length left at 'curr'. */
} else {
/* Silly user, did not start the line with a number. */
}
Although sscanf() returns the number of successful conversions, the implementations differ on whether %n (which means "store the number of characters consumed thus far to an int") is included as a conversion or not. The above is a safe workaround.
Whether you use this unlimited-line-length approach or not, is completely up to you; I'm sure you can see why it would be useful in real world applications. For learning purposes or an exercise, it might be just unnecessary added complexity. However, because it is so useful in practice, I wanted to show how to do it.
Originally Posted by
Mr.Lnx
the memory layout will be
Pretty much right, yes.
There is optional padding at the end of each structure, because malloc() allocates in aligned chunks. (That padding just makes the size of the allocated memory chunk a multiple of that alignment.)
Depending on how the C library implements memory allocation, there is often also metadata stored before each chunk. So, if we're really precise, the memory layout is likely to be something like
Code:
[ C library metadata for dynamically allocated chunk 1 ]
size == 5
[ possible padding between an int and a char ]
s[0]
s[1]
s[2]
s[3]
s[4]
s[5] == '\0'
[ possible padding depending on the C library ]
[ C library metadata for dynamically allocated chunk 2 ]
size == 5
[ possible padding between an int and a char ]
s[0]
s[1]
s[2]
s[3]
s[4]
s[5] == '\0'
[ possible padding depending on the C library ]
[ C library metadata for dynamically allocated chunk 3 ]
size == 5
[ possible padding between an int and a char ]
s[0]
s[1]
s[2]
s[3]
s[4]
s[5] == '\0'
[ possible padding depending on the C library ]
[ C library metadata for dynamically allocated chunk 4 ]
size == 5
[ possible padding between an int and a char ]
s[0]
s[1]
s[2]
s[3]
s[4]
s[5] == '\0'
[ possible padding depending on the C library ]
[ C library metadata for dynamically allocated chunk 5 ]
size == 5
[ possible padding between an int and a char ]
s[0]
s[1]
s[2]
s[3]
s[4]
s[5] == '\0'
[ possible padding depending on the C library ]
but the amount of padding depends on both the compiler (it selects the best one for the architecture) and the C library (as it chooses the alignment for the chunks), and the metadata depends on the C library malloc() implementation. And there is no guarantee the chunks are consecutive in memory, either.
(Technically, most C libraries extend the uninitialized data segment using architecture-specific implementation of sbrk() for small allocations, and switch to memory-mapping for large allocations. This means that freeing a lot of small allocations is unlikely to release the memory back to the operating system, but freeing a large allocation is likely to be released back to the operating system. But we're getting well outside what the C standards tell us, into the practical details of most implementations, which are subject to differ and change.)
Because the metadata often precedes the structure itself, writing to flexible array members at negative indexes or exceeding the amount of memory allocated to a previous structure in memory, garbles the metadata. Such bugs are often only seen when you attempt to free() the structures. The errors seen vary from C library to another, but any error at free() time is almost certainly an indication that you accidentally wrote to a dynamically allocated memory past the allocated amount. (Further complication is the allocation padding, as often you have something like one to 15 extra char's available in the structure, before you start overwriting the metadata in another dynamically allocated structure.)
Anyway, it does explain why, if you see library errors or crashes at free(), the almost guaranteed culprit is a write out of bounds in one of your dynamically allocated structures. I've never seen any other reason, actually