0

I am trying to write a program in C to take the data from the input.txt file and insert it into the record.txt file in the ascending order of the students’ ID.

Content of input.txt:

1
2012 Bob CS21
1999 Teddy CS35
2
3
2001 Eric CS11
2011 CS12 CS87

Content of record.txt:

1287 Nancy CS11
1865 Brown CS33

When I run the program, the following data from input.txt is supposed to be inserted into the record.file(valid data with students' ID, name and course):

2012 Bob CS21
1999 Teddy CS35
2001 Eric CS11

Then the content of record.txt file after insertion(ascending order):

1287 Nancy CS11
1865 Brown CS33
1999 Teddy CS35
2001 Eric CS11
2012 Bob CS21

Below is my code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 100

typedef struct {
int id;
    char name[20];
    char course[5];
} Student;

int read_records(Student s[]) {
    FILE *fp;
    int i=0;
    fp = fopen("record.txt", "r");
    if (fp == NULL) {
        printf("File doesn't exist\n");
        return 0;
    }
    while (!feof(fp)) {
        fscanf(fp, "%d %s %s\n", &s[i].id, s[i].name, s[i].course);
        i++;
    }
    fclose(fp);
    return i;
}

void write_records(Student s[], int n) {
    FILE *fp;
    fp = fopen("record.txt", "w");
    if (fp == NULL) {
        printf("File doesn't exist\n");
        return;
    }
    for (int i=0; i<n; i++) {
        fprintf(fp, "%d %s %s\n", s[i].id, s[i].name, s[i].course);
    }
    fclose(fp);
}

void insert_records(Student s[], int n) {
    FILE *fp;
    fp = fopen("input.txt", "r");
    if (fp == NULL) {
        printf("File doesn't exist\n");
        return;
    }
    int i=n;
    while (!feof(fp)) {
        fscanf(fp, "%d %s %s\n", &s[i].id, s[i].name, s[i].course);
        i++;
    }
    fclose(fp);
    write_records(s, i);
    printf("Insertion is done.\n");
}
void display_records(Student s[], int n) {
    for (int i=0; i<n; i++) {
        printf("%d %s %s\n", s[i].id, s[i].name, s[i].course);
    }
}
int main() {
    int n;
    Student s[MAX];
    n = read_records(s);
    int opt;
    while (1) {
        printf("1. Insert\n");
        printf("2. Display\n");
        printf("3. Exit\n");
        printf("Choose an option: ");
        scanf("%d", &opt);
        switch(opt) {
            case 1: insert_records(s, n);
                    break;
            case 2: display_records(s, n);
                    break;
            case 3: exit(0);
        }
    }
}

When I run the program, the user will be asked to choose an option.

If I enter 2, it will display the content of record.txt. And it works well, as I expected.

If I enter 1, the insertion should be performed and "Insertion is done" will be printed. However, the program doesn't work as I expected. It just displays nothing.

I am confused about that, and I would like to know how to fix the program.

Bob
  • 1
  • 1
  • 3
    Obligatory aside: [Why is “while( !feof(file) )” always wrong?](https://stackoverflow.com/q/5431941/2505965) – Oka Nov 18 '22 at 20:55
  • Another: [What is the effect of trailing white space in a scanf() format string?](https://stackoverflow.com/questions/19499060/what-is-the-effect-of-trailing-white-space-in-a-scanf-format-string) The format specifiers you use automatically filter leading whitespace (but `%c` and `%[]` would not). – Weather Vane Nov 18 '22 at 20:56

1 Answers1

0

This loop

while (!feof(fp)) {
    fscanf(fp, "%d %s %s\n", &s[i].id, s[i].name, s[i].course);
    i++;
}

will run indefinitely1 because when fscanf encounters input it cannot convert, that input is left in the stream. The file will never exhaust.

With the file

1
2012 Bob CS21
1999 Teddy CS35
2
3
2001 Eric CS11
2011 CS12 CS87

the first fscanf call will convert 1 "2012" "Bob". The second call and so on will fail to convert CS21 into an integer.

Additionally, see Why is “while( !feof(file) )” always wrong?.

Also note that an unbounded %s specifier in *scanf functions is as dangerous as gets. Use a field-width specifier to limit the amount of data that can be read into your buffers. This should be the maximum allowed string length, and should be at most the size of your buffer minus one, leaving room for the null-terminating byte. (i.e., char s[256]; scanf("%255s", s);).

The solution is to never ignore the return values of the fscanf family of functions. Use these return values to determine how your program should proceed. If fscanf fails to match the expected number of conversions, you must be ready to clean up the input stream. This usually means consuming characters until the end of a line is found.

This is easier said than done, however. As seen above, the way %s skips whitespace (including newline characters) means it can overreach looking for valid characters.

The general suggestion is to avoid scanf/fscanf, and instead read entire lines with fgets and then parse those lines with sscanf (or other tools).

In practice this looks like:

#include <stdio.h>
#include <stdlib.h>

#define INPUT "input.txt"

int main(void)
{
    FILE *file = fopen(INPUT, "r");

    if (!file) {
        perror(INPUT);
        return EXIT_FAILURE;
    }

    struct {
        int id;
        char name[32];
        char course[16];
    } student;

    char buffer[512];

    while (fgets(buffer, sizeof buffer, file)) {
        int cv = sscanf(buffer, "%d%31s%15s",
                &student.id, student.name, student.course);

        if (3 == cv) {
            /* three successful conversions, do whatever with `student` */
            printf("%s<%d> [%s]\n",
                    student.name, student.id, student.course);
        }
    }

    fclose(file);
}

With your input.txt file this prints:

Bob<2012> [CS21]
Teddy<1999> [CS35]
Eric<2001> [CS11]
CS12<2011> [CS87]

Once you know sscanf succeeded in parsing the expected number of conversions you can move on to validating the record (e.g., "CS12" is an invalid name, per your examples, and you probably need a way to avoid duplicate entries).

In your code you should only increment i after making sure all these steps are followed.

After you have your merged list of records, you should use qsort to sort the array with a comparison function such as

int student_sort(const void *va, const void *vb)
{
    const Student *a = va, *b = vb;

    return (a->id > b->id) - (a->id < b->id);
}

before writing the records out.


1. i will eventually overflow, invoking Undefined Behaviour. The outcome of the program after this point cannot be generally reasoned about.

Oka
  • 23,367
  • 6
  • 42
  • 53