this function will rewind file, create the dynamic array (of size), and read in the data, populating the _data struct dynamic array. Note that stream
is passed by value this time. The function then returns the populated
array of struct
struct _data
{
char* name;
long number;
};
struct _data *load(FILE *stream, int size)
{
struct _data BlackBox = calloc(size, sizeof(_data));
char tempName[3];
stream = fopen("names.txt", "r");
for (int i=0; i<size; i++)
{
fscanf(stream, "%s %ld", tempName, &data.number);
BlackBox[i].name = calloc(strlen(tempName), sizeof(char));
strcpy(BlackBox[i].name, tempName);
}
fclose(stream);
return &BlackBox;
}
File Content
ron 7774013
jon 7774014
I am a beginner and having difficulty designing the code. Can someone please explain. Thanks
I think you have some warning from gcc to help you.
Fix memory management with your calloc, and don't return a stack pointer
typedef struct _data
{
char* name;
long number;
} _data;
_data *load(FILE *stream, int size)
{
_data *BlackBox = calloc(size, sizeof(_data));
char tempName[3];
for (int i=0; i<size; i++)
{
fscanf(stream, "%s %ld", tempName, &BlackBox[i].number);
BlackBox[i].name = strdup(tempName);
}
fclose(stream);
return BlackBox;
}
int main (void)
{
FILE *f = fopen("test.data", "r");
_data *data = load(f, 2);
printf("%s %ld\n", data[0].name, data[0].number);
printf("%s %ld\n", data[1].name, data[1].number);
return 0;
}
output
aurel@vm-pontarlier:~$ ./a.out
ron 7774013
jon 7774014
Think about change _data
typedef struct _data{
char name[256];
long number;
} _data;
scan will be:
for (int i=0; i<size; i++)
{
fscanf(stream, "%s %ld", BlackBox[i].name, &BlackBox[i].number);
}
You are making the same arrors as with your previous post. You also don't allocate memory for the name
member in _data
. As for the compilation errors:
- Arrays of type
T
that you allocate dynamically with malloc
or calloc
are controlled by a handle, a pointer to T
of type T*
.
- The structure you defined is of type
struct _data
, the type includes the keyword struct
. If you want a one-word identifier for your type, use `typedef´ as Ôrel has shown you.
- You have no variable called ´data
. The handle to the newly allocated memory is ´BlackBox
.
If you fix these errors, you will run into logical errors that the compiler cannot know about:
- Your
BlackBox[i].name
is initialised to NULL
via calloc
, so it doesn't point to valid memory. You cannot strcpy
anything into it. What you can do is to use the non-standard, but widely available strdup
, which first allocates memory as required and then copies.
- Your temporary string is of length 3; it can hold at most two characters plus the null terminator. The names in your file are short, granted, but your program must prepare for any input, even illegal input.
- The
FILE *
isn't used outside your function; it should be local to load
.
- The user must specify how many items to read. This defies the purpose of dynamic allocation. Also the user can't know how many items were read; there might be fewer items in the file as requested. Your function design should reflect that. (Okay, thst's not really true: You zero-initialise the memory, which means a char pointer of
NULL
signals the end of the array, but:)
- You don't test the output of
fscanf
, which you should. If your file has fewer than size
items, the last items will contain garbage, because you copy garbage values into the zero initialised memory.
- Not an error per se, but if your file is organised along lines, consider reading a line with
fgets
first and then parsing that with sscanf
.
The example code below tries to incorporate these guidelines. Things to note:
- The memory isn't allocated in one huge chunk, but it grows as needed with subsequent calls to
realloc
. The call realloc(NULL, s)
is equivalent to malloc(s)
.
- The functions "fills in" the number of elementsread via a pointer to an integer.
- The strings are copied into newly allocated memory. That means that in theory, the strings can e as long as they want. (In practice, the max buffer length of 20 cuts such strings short.) The function
duplicate
emulates the function strdup
.
- These strings should be
free
d when cleaning up.
- The
load
function takes a filename istead of a file handle.
- File reading takes place in two stages: Read a line first, then scan this line. If the format is not "string without spaces" + "number", an error message is written.
Anyway, here goes:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct _data
{
char *name;
long number;
};
/*
* Duplicate a string on the heap (aka strdup)
*/
char *duplicate(const char *str)
{
char *p = malloc(strlen(str) + 1);
strcpy(p, str);
return p;
}
/*
* Read a list of names from file fn
*/
struct _data *load(const char *fn, int *psize)
{
struct _data *data = NULL;
FILE *stream;
char line[80]; // buffer for line
int lnr = 0; // line number for error message
int n = 0; // number of read items
stream = fopen("names.txt", "r");
if (stream == NULL) return NULL; // Can't open file
while (fgets(line, sizeof(line), stream)) {
long int number;
char buf[20];
lnr++;
if (sscanf(line, "%19s %ld", buf, &number) == 2) {
data = realloc(data, (n + 1) * sizeof(*data));
data[n].number = number;
data[n].name = duplicate(buf);
n++;
} else {
fprintf(stderr, "[%s, line %d] Illegal format\n", fn, lnr);
}
}
fclose(stream);
*psize = n; // Assign number of read items
return data;
}
/*
* Free memory allocated by load
*/
void cleanup(struct _data *data, int n)
{
while (n--) free(data[n].name);
free(data);
}
int main()
{
struct _data *data;
int i, n;
data = load("names.txt", &n);
if (data == NULL) return -1;
for (i = 0; i < n; i++) {
printf("%-20s%12ld\n", data[i].name, data[i].number);
}
cleanup(data, n);
return 0;
}
...and lastly tempName[3] is too small: it must be at least 4, given your 3 letter inputs. Further, you forget to allocate room for the terminating null character in your malloc call:
char tempName[4];
...
BlackBox[i].name = malloc(strlen(tempName)+1);
(That tempName[3] did not lead to errors is because the compiler probably rounded it up to an even number of bytes - but it is a typical beginner's fault.)