1

I've got a problem regarding Dynamical Allocation & Structs. The task: I have a struct of students, which looks like that:

typedef struct{
  unsigned id;
  char *lastName;
  float grade;
}students_t;

I'm not allowed to pass to lastName a maximum number of characters, it must remain a pointer which size I will increase every time.

My code looks like this:

unsigned counter = 0;
students_t* students = NULL;
students_t temp;

char char_current;
unsigned char_counter=0;

while (fscanf(inputFile,"%u",&temp.id) == 1) {

    students = realloc(students,(counter+1) * sizeof(students_t));
    students[counter].id=temp.id;
    printf("%d",students[counter].id);

    students[counter].lastName = NULL;
    while (fscanf(inputFile,"%c",&char_current) != ' ') {
        students[counter].lastName = realloc(students[counter].lastName,(char_counter+1) * sizeof(char));
        students[counter].lastName[char_counter] = char_current;

        char_counter++;
    }

    students[counter].lastName[char_counter] = '\0';

    fscanf(inputFile,"%f",&students[counter].grade);


    counter++;
}

My problem is with the fscanf from the while (because the program enters an infinite loop), but I don't know how to actually fix it.

I would be grateful if someone could help me figure it out. Thank you!

Saucy Goat
  • 1,587
  • 1
  • 11
  • 32
darksz
  • 115
  • 2
  • 12

1 Answers1

2

You have several problems:

  1. The while() loop isn't terminating (your initial question).
  2. fscanf() is unsafe - there are better alternatives.
  3. You're using fscanf() incorrectly.
  4. Reading a string a character at a time is inefficient.
  5. Repeatedly calling "realloc()" is inefficient - there are better alternatives.

Here is some example code.

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

#define MAX_STRING 80

typedef struct {
   unsigned id;
   char *lastName;
   float grade;
} students_t;

students_t* enter_new_student (FILE *inputFile)
{
   char buffer[MAX_STRING];
   unsigned id;
   int iret;

   // Check for end of input
   iret = fscanf(inputFile, "%u", &id);
   if ((iret < 1) || feof(inputFile)) {  // iret should be "1" if successful
      return NULL;
   }

   // Allocate a record and read its data
   students_t *student = (students_t *)malloc(sizeof(students_t));
   iret = fscanf(inputFile, "%s %f", buffer, &student->grade); // iret should be "2" if successful
   student->id = id;
   student->lastName = strdup(buffer);   // strdup() does an implicit "malloc()" and "strcpy()"

   // Return new student
   return student;
}

int main()
{
   students_t *student = NULL;
   int record_counter = 0;
   FILE *fp;

   // Open file
   if (!(fp = fopen("tmp.txt", "r"))) {
      perror("unable to open file");
      return 1;
   }

   // Read student records
   while ((student = enter_new_student(fp))) {
      if (student) {
         ++record_counter;
         printf("new student=%s,id=%u, grade=%f, record_counter=%d\n",
            student->lastName, student->id, student->grade, record_counter);
      }
   }

   // Done
   printf("Done: final record count=%d\n", record_counter);
   return 0;
}

Here is a sample "tmp.txt" file:

1 Johnson 4.0
2 Jackson 3.5
3 Jamison 3.85

And corresponding sample output:

new student=Johnson,id=1, grade=4.000000, record_counter=1
new student=Jackson,id=2, grade=3.500000, record_counter=2
new student=Jamison,id=3, grade=3.850000, record_counter=3

In general, prefer using fgets() over fscanf(): Disadvantages of scanf

Notice that I put everything having to do with reading a student record inside a separate function: enter_new_student(). You'll also notice that the "control structure" - the "while loop" is OUTSIDE of the function.

There are two (related) conditions that can cause the loop to exit:

  1. Didn't read "id"
  2. End of file

The reason your original "while loop" failed was that fscanf() will never return ' ' ... so you inadvertently coded an "infinite loop". Here's why:

https://linux.die.net/man/3/fscanf

Return Value

These functions return the number of input items successfully matched and assigned, which can be fewer than provided for, or even zero in the event of an early matching failure.

The value EOF is returned if the end of input is reached before either the first successful conversion or a matching failure occurs. EOF is also returned if a read error occurs, in which case the error indicator for the stream (see ferror(3)) is set, and errno is set indicate the error.

FoggyDay
  • 11,962
  • 4
  • 34
  • 48
  • Hello @FoggyDay , about the fscanf - this is what they want us to use. And I know that realloc() repeatedly is inefficent in terms of time, but this is what they want us to use. (Probably to see that we are capable of doing it or I don't know). – darksz Jan 10 '20 at 00:40