andrisll
andrisll

Reputation: 31

Appending to a file in C, reading to an array of struct

The program asks user to input some records (structures) if necessary and appends them to the existing file or creates a new file if there isn't any, then lists the contents of the file.

#include <stdio.h>
#include <string.h>
#define N 25

int main() {

  struct studrec {
    char name[20], surname[20], sex, date[12];
  } students[N];
  int i, count = 0;
  char another;

  FILE *fileptr;

  for (i = 0; i < 10; i++) {
    puts("Press y to continue without adding new records");
    another = getchar();
    if (another == 'y' || another == 'Y') break;
    while ((another = getchar()) != '\n' && another != EOF);
    puts("Input info");

    puts("Name: ");
    if (fgets(students[i].name, sizeof(students[i].name), stdin) == NULL) return 1;
    students[i].name[strlen(students[i].name)-1] = '\0';

    puts("Surname: ");
    if (fgets(students[i].surname, sizeof(students[i].surname), stdin) == NULL) return 1;
    students[i].surname[strlen(students[i].surname)-1] = '\0';

    puts("Sex (m/f): ");
    students[i].sex = getchar();
    while ((another = getchar()) != '\n' && another != EOF);

    puts("Date (dd.mm.yyyy): ");
    if (fgets(students[i].date, sizeof(students[i].date), stdin) == NULL) return 1;
    students[i].date[strlen(students[i].date)-1] = '\0';
    while ((another = getchar()) != '\n' && another != EOF);
   }
   count = i;

   fileptr = fopen("students.txt", "a+");
   for (i = 0; i < count; i++) fwrite(&students, sizeof(students), 1, fileptr);

   rewind(fileptr);
   for (i = 0; (another = fgetc(fileptr)) != EOF && i < N; i++) {
     fseek(fileptr, -1, SEEK_CUR);
     fread(&students, sizeof(students), 1, fileptr);
   }
   fclose(fileptr);

   count = i;
   for (i = 0; i < count; i++) printf("%20s%20s%4c%15s\n", students[i].name, students[i].surname, students[i].sex, students[i].date);

   return 0;
}

Everything works normally when writing to a new file. Output:

...input procedure...
Press y to continue without adding new records
y
                Liam               James   m     12.03.1987
               Abbey             Trueman   f     23.07.1943
                Hugo               Brown   m     13.05.1947

But then if I run it again and try to append another record to the existing file the program fails:

...input procedure...
Press y to continue without adding new records
y
               Nadia          Rachmonoff   f     12.07.1934
                                    O|u                  
                  �u                       �              u
                                           �           E�u

It seems that the new record is put in students[0] and all the other elements are erased. What am I doing wrong? Maybe there's something wrong with the &students pointer. I tried with &students[i] but it returned "segmentation fault" after the first iteration. As I understand the &students address is "automatically" incremented to the next element after every fread/fwrite. If it wasn't so the program would not work correctly on the first run.

Upvotes: 1

Views: 4792

Answers (3)

Dmitri
Dmitri

Reputation: 9375

You have a few issues with how you deal with the arrays. First of all, this line:

for (i = 0; i < count; i++) fwrite(&students, sizeof(students), 1, fileptr);

What you're doing here running a loop, with one pass for each record entered, and writing the whole array of 25 items to the file on each pass. So if 3 records were entered, you write 75, most of which are garbage or redundant. What you probably want here is more like this:

for (i = 0; i < count; i++)
  fwrite(&students[i], sizeof(students[i]), 1, fileptr);

...where the size is the size of one element of the array (a single record) and the address is the address of the record you're currently writing rather than the address of the whole array.

You have a similar problem when reading the data later, among others:

rewind(fileptr);
for (i = 0; (another = fgetc(fileptr)) != EOF && i < N; i++) {
  fseek(fileptr, -1, SEEK_CUR);
  fread(&students, sizeof(students), 1, fileptr);
}

First, the fread(&students, sizeof(students), 1, fileptr); should be changed much like the fwrite() before, to fread(&students[i], sizeof(students[i]), 1, fileptr);. The reasons are the same: You're reading one record at a time in a loop, so you need to read into that item not just to the beginning of the array each time, and the amount of data you read should be the size of that record rather than the size of the whole array.

Second, though fixing the above should get it working, you could easily change the loop so that you don't need to read the single char in the loop expression to check for EOF. You should really check for that by checking the return from the fread() in the loop instead. Something like:

rewind(fileptr);
for (i = 0; i < N; i++) {
  if (fread(&students[i], sizeof(students[i]), 1, fileptr) != 1)
    break;
}

Or better yet, just read N items with no loop, and use the return value to tell you how many were read:

rewind(fileptr);
i = fread(students, sizeof(students[0]), N, fileptr);

Upvotes: 3

user411313
user411313

Reputation: 3990

Your writing code is incorrect, try

   fwrite(students, sizeof*students, i, fileptr);

   rewind(fileptr);
   for (i = 0; i < N && fread(&students[i], sizeof*students, 1, fileptr); i++);

Upvotes: 0

NPE
NPE

Reputation: 500217

The following writes the entire 25-element array count times:

for (i = 0; i < count; i++) fwrite(&students, sizeof(students), 1, fileptr);

There are two ways to fix this:

  1. Compute the correct size occupied by the count elements, and write them out with a single fwrite call.
  2. Write the entries out one a time, using a loop.

The same goes for the loop that tries to read the elements back: each iteration reads the entire 25-element array.

Upvotes: 0

Related Questions