I'm afraid you don't realize how far you are from getting it right. Sit tight, this is going to be long. Welcome to C.
char** monthGroup
All this really means is "a pointer-to-pointer-to-char
". However, C has many reasons why you would want to point to something. In your case, the "inner" pointing is so that you can actually point at a sequence of char
s in memory (which you colloquially treat as a "string", which C properly does not have), and the "outer" pointing is so that you can point at a sequence of those char*
s, and treat that sequence as an "array" (even though it isn't; you're going to dynamically allocate it).
Here's the problem: When you pass in this char**
that came from main
, it doesn't actually point at anything. "That's fine", you say; "the function is going to make it point at some memory that I'll allocate with malloc()
".
Nope.
C passes everything by value. The char**
that months
receives is a copy of the char**
in main
's chunk of local variables. You overwrite the pointer (with the result of the malloc
call), write some pointers into that pointed-at memory (more malloc
results), copy some data into those chunks of pointed-at memory... and then, at the end of the function, the parameter monthGroup
(which is a local variable in months
) no longer exists, and you've lost all that data, and the variable monthGroup
in main is still unchanged at pointing at nothing. When you try to use it as if it points at something, boom you're dead.
So how do we get around this? With another level of pointing, of course, C properly does not have "pass by reference", so we must fake it. We accept a char***
, and pass it &monthGroup
. This is still a copied value, but it points directly into the local variable storage for that invocation of main
(on the stack). That lets us write a value that will be visible in main
. We assign the first malloc
result to *monthGroup
, and write pointers into that storage (*monthGroup[count]
), etc.
Except we don't really want to do that, because it's incredibly ugly and confusing and hard to get right. Let's instead do what should be an incredibly obvious thing that you're meant to do and that basic instruction doesn't emphasize nearly enough: use the return value of the function to return the result of the calculation - that's why it's called the return value.
That is, we set up a char**
in months
(not accepting any kind of parameter for it), return it, and use it to initialize the value in main
.
Are we done? No.
You still have some logical errors:
- You re-allocate the "outer" layer within your while-loop. That's clearly not what you want; you're allocating several "strings", but only one "array", so that allocation goes outside the loop. Otherwise, you throw away (without properly deallocating them!) the old arrays each time.
Actually, you do want to do something like this, but only because you don't know in advance how many elements you need. The problem is that the new allocation is just that - a new allocation - not containing the previously-set-up pointers.
Fortunately, C has a solution for this: realloc
. This will allocate the new memory, copy the old contents across (the pointers to your allocated "strings"), and deallocate the old chunk. Hooray! Better yet, realloc
will behave like malloc
if we give it a NULL pointer for the "old memory". That lets us avoid special-casing our loop.
- You're using the value
count
incorrectly. The first time through the loop, you'll increment count
to 1, allocate some space for monthGroup[1]
to point at, and then attempt to write into the space pointed at by monthGroup[0]
, which was never set up. You want to write into the same space for a "string" that you just allocated. (BTW, sizeof(char)
is useless: it is always 1. Even if your system uses more than 8 bits to represent a char! The char
is the fundamental unit of storage on your system.)
Except not, because there's a simpler way: use strdup
to get a pointer to an allocated copy of your buffer.
char** months(FILE* monthfp) {
char buffer[50];
int count = 0;
char** monthGroup = NULL;
while (fgets(buffer, sizeof(buffer), monthfp) != NULL) {
// (re-)allocate the storage:
monthGroup = realloc(monthGroup, count * sizeof(char*));
// ask for a duplicate of the buffer contents, and put a pointer to the
// duplicate sequence into the last element of the storage:
monthGroup[count - 1] = strdup(buffer);
}
return monthGroup;
}
Adjusting main
to match is left as a (hopefully trivial) exercise. Please also read the documentation for realloc
and strdup
.
Are we done? No.
You should still be checking for NULL
returns from realloc
and strdup
(since they both attempt to allocate memory, and thus may fail in that way in C), and you still need code to free
the allocated memory.
And, as others pointed out, you shouldn't be assuming there will be 12 months. If you could assume that, you wouldn't be dynamically allocating monthGroup
in the first place; you'd just use an array. So you need to communicate the size of the result "array" somehow (adding an explicit NULL pointer to the end is one way; another is to do the horribly ugly thing, pass in a char***
, and use the return value to count the size).