I've created an array of structs (calloc
for initial, realloc
for subsequent "elements"). Realloc/initialisation is triggered by each line read from a text file using fgets
.
My problem is that I expect a certain amount of struct array elements to be created, but in reality I end up with double the amount. Every second element's data is correctly stored, with empty and/or erroneous data stored in every other element.
Note: This question follows on from a related, but different, problem for the same program: Error: free(): double free detected in tcache 2. Calloc/Realloc Array of Structs in C when triggered by fgets.
The format of the text file per line is:
Single letter + space + single letter + (almost certainly) newline e.g.
A X
C Z
B Y
etc. etc.
Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define GROWBY 100
#define BUFFER 100
// The struct definition is:
typedef struct a_struct {
// Used to store a single char, not sure if there's a better way?
char firstChar[2];
char secondChar[2];
int value;
} TheStruct;
// The initial struct is created thus:
TheStruct *
new_struct(void)
{
TheStruct *aStruct = NULL;
aStruct = calloc(1, sizeof(TheStruct) + 1);
return aStruct;
}
// New struct elements are realloc'd dependent on a buffer size, then
// initialised with empty/zeroed data:
TheStruct *
add_struct_to_array(TheStruct * aStruct, int *numElements, int *bufferLen)
{
char *init = NULL;
if (*numElements >= *bufferLen) {
*bufferLen += GROWBY;
TheStruct *newStruct = realloc(aStruct, *bufferLen * (sizeof(TheStruct) + 1));
if (newStruct == NULL) {
free(aStruct);
printf("Error: Memory could not be allocated.");
exit(EXIT_FAILURE);
}
else if (aStruct != newStruct) {
aStruct = newStruct;
}
newStruct = NULL;
init = NULL;
}
*numElements += 1;
char first[2] = { "0" };
char second[2] = { "0" };
strcpy(aStruct[*numElements].firstChar, first);
strcpy(aStruct[*numElements].secondChar, second);
aStruct[*numElements].value = 0;
return aStruct;
}
// I then parse a line of string data from the text file to then store in a
// struct element. The two chars per line are used to determine the value of
// the struct:
void
parse_buffer(TheStruct * aStruct, char *buffer, int *numElements)
{
char charOne[2] = "",
charTwo[2] = "";
sscanf(buffer, "%s %s", charOne, charTwo);
strcpy(aStruct[*numElements].firstChar, charOne);
strcpy(aStruct[*numElements].secondChar, charTwo);
if (*charOne == 'A') {
if (*charTwo == 'X') {
aStruct[*numElements].value = 6;
}
else if (*charTwo == 'Y') {
aStruct[*numElements].value = 8;
}
else if (*charTwo == 'Z') {
aStruct[*numElements].value = 1;
}
}
else if (*charOne == 'B') {
if (*charTwo == 'X') {
aStruct[*numElements].value = 1;
}
else if (*charTwo == 'Y') {
aStruct[*numElements].value = 6;
}
else if (*charTwo == 'Z') {
aStruct[*numElements].value = 8;
}
}
else if (*charOne == 'C') {
if (*charTwo == 'X') {
aStruct[*numElements].value = 8;
}
else if (*charTwo == 'Y') {
aStruct[*numElements].value = 1;
}
else if (*charTwo == 'Z') {
aStruct[*numElements].value = 6;
}
}
}
// I drive it all through main as so:
void
main()
{
TheStruct *aStruct = new_struct();
int structCreated = 0;
int bufferLen = 0,
numElements = 0;
char buffer[BUFFER];
FILE *file = fopen("input.txt", "r");
while (fgets(buffer, sizeof(buffer), file) != NULL) {
if (!structCreated) {
structCreated = 1;
numElements += 1;
}
else {
aStruct = add_struct_to_array(aStruct, &numElements, &bufferLen);
}
parse_buffer(aStruct, buffer, &numElements);
}
// Free memory
fclose(file);
free(aStruct);
}
Valgrind has no problems, but if I print out the data of a struct, I would expect e.g.:
A Y 6
Instead, I get:
0
A Y 6
Secondly, if I count the number of times my add_struct_to_array
function is called (and thus my expected number of struct array elements), I get double the amount I expect.
I suspect there's a problem in calling add_struct_to_array
each time fgets is called - perhaps it gets called twice per line due to the trailing newline as per the following: Removing trailing newline character from fgets() input?
I tried that answer's approach in using strscpn
to essentially ignore the newline call, but in doing so while I ended up with the correct number of struct array elements, it'd still be the case that only every second one would be populated with data and every other one would have erroneous data.