简体   繁体   中英

Conditional statement in loop checks against variable set when fscanf returns a value different than 1 causes an infinite loop

I'm expecting the loop to end when the end of the file is reached. I know that when it reaches it the value returned from fscanf will be different than 1, as it returns 1 whenever it reads something.

If I set i=3 the loop is infinite, but if I set i=2 the loop ends, which I find very weird, as the controlling expression (i!=3) is supposed to be evaluated before the adjustment one, so when I set i=3 , it breaks and should test than indeed i!=3 so the loop would end (which it doesn't). When I set it to 2, it ends, so it must be incrementing it one more time and then checking.

So, my first question is why is this happening?

My second question is that with the %[^\\n]s it only saves the start of the file, but with only %s it saves the whole file, but wrongly as it only scans until the space, but I want it to scanf until the new line.

My file has 1 element per line (some with spaces)

for(int i=0;i!=3;i++){
    switch(i){
      case 0:
        if(fscanf(recordsRegistry,"%[^\n]s", (recordsArray[recordsArrayPosition]).author)!=1){
          i=3;//stop condition
        }
        break;
      case 1:
        if(fscanf(recordsRegistry,"%[^\n]s", (recordsArray[recordsArrayPosition]).title)!=1){
          i=3;//stop condition
        }
        break;
      case 2:
        if(fscanf(recordsRegistry,"%hu", &((recordsArray[recordsArrayPosition]).numberOfSales))!=1){
          i=3;//stop condition
        }
        i=-1;
        recordsArrayPosition++;
        totalRecords++;
        recordsArray=realloc(recordsArray, totalRecords*recordStructSize) ;
        if(recordsArray==NULL){
          fprintf(stderr, "Could not reallocate memory at line %d.\n", __LINE__);
          return 3;
        }  
        break;
    }
  }

Example of the file being read:

LEANN RIMES
WHAT A WONDERFUL WORLD
4628
BLUE CHEER
WHAT DOESNT KILL YOU
9664
WITHIN TEMPTATION & KEITH CAPUTO
WHAT HAVE YOU DONE
3226
WITHIN TEMPATION
WHAT HAVE YOU DONE
8093
KOKO TAYLOR
WHAT IT TAKES (THE CHESS YEARS)
7160
DOOBIE BROTHERS
WHAT WERE ONCE VICES ARE NOW HABITS
2972
LIL'ED & THE BLUES IMPERIALS
WHAT YOU SEE IS WHAT YOU GET
9443
VARIOUS ARTISTS
WHAT'S SHAKIN
4473

The struct:

typedef struct{
  char author[20], title[50];
  short unsigned int numberOfSales;
} RECORD;

New for looop:

for(int i=0;i!=3;i++){
    switch(i){
      case 0:
        if(fgets(recordsArray[recordsArrayPosition].author, totalRecords, recordsRegistry)==NULL){
          //printf("aa\n");
          i=2;//stop condition
        }
        break;
      case 1:
        if(fgets(recordsArray[recordsArrayPosition].title, totalRecords, recordsRegistry)==NULL){
          //printf("aaa\n");
          i=2;//stop condition
        }
        break;
      case 2:
        if(fscanf(recordsRegistry,"%hu", &((recordsArray[recordsArrayPosition]).numberOfSales))!=1){
          //printf("aaaa\n");
          i=2;//stop condition
        }
        i=-1;
        recordsArrayPosition++;
        totalRecords++;
        recordsArray=realloc(recordsArray, totalRecords*recordStructSize) ;
        if(recordsArray==NULL){
          fprintf(stderr, "Could not reallocate memory at line %d.\n", __LINE__);
          return 3;
        }  
        break;
    }
  }

它打印什么

... when fscanf returns a value different than 1 causes an infinite loop

when you set i to 3 that value will not be tested in i!=3 because before the test the i++ will be done

set i to 2


with only %s ... it only scans until the space I want it to scanf until the new line.

if you want to read line per line use fgets rather than fscanf , do not forget to remove the probable newline

in the scanf family 's' matches a sequence of non-white-space characters, spaces are separator

man scanf says :

   s      Matches a  sequence  of  non-white-space  characters;  the  next
          pointer  must be a pointer to the initial element of a character
          array that is long enough to hold the  input  sequence  and  the
          terminating null byte ('\0'), which is added automatically.  The
          input string stops at white space or at the maximum field width,
          whichever occurs first.

warning you mix to read line and value, when you read the value the newline is not read, replace "%hu" by "%hu\\n" or much secure read the line then extract the number from it (I do that in my proposal)


from your remark

why will i++ test before i!=3?

your :

 for(int i=0;i!=3;i++){ <body without continue> }

is equivalent to

{ int i = 0;

  while (i != 3) {
    <body without continue>
    i++;
  }
}

Here a proposal :

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

typedef struct{
  char author[20], title[50];
  short unsigned int numberOfSales;
} RECORD;

#define MAXRECORDS 100

void removeEndSpaces(char * s)
{
  char * p = s + strlen(s);

  while ((s != p) && isspace((unsigned char) *--p))
    *p = 0;
}

int main()
{
  FILE * fp = fopen("f", "r");
  RECORD records[MAXRECORDS];
  int nrecords;
  char line[32];

  if (fp == NULL){
    perror("cannot read f");
    return -1;
  }

  for (nrecords = 0; nrecords != MAXRECORDS; nrecords += 1) {
    if (fgets(records[nrecords].author, sizeof(records[nrecords].author), fp) == NULL)
      break;
    removeEndSpaces(records[nrecords].author);

    if (fgets(records[nrecords].title, sizeof(records[nrecords].title), fp) == NULL) {
      fprintf(stderr, "invalid input file\n");
      break;
    }
    removeEndSpaces(records[nrecords].title);

    /* the more secure way to read the number is first to read the line then read the enumber in that line */
    if ((fgets(line, sizeof(line), fp) == NULL) ||
        (sscanf(line, "%hu", &records[nrecords].numberOfSales) != 1)) {
      fprintf(stderr, "invalid input file\n");
      break;
    }
  }

  /* nrecords values the number of records read without error */
  for (int i = 0; i != nrecords; i += 1)
    printf("%s : %s / %hu\n", 
           records[i].author, records[i].title, records[i].numberOfSales);

  return 0;
}

As you see it is useless to do your stuff with the index and the code is more clear

Supposing the file f contains your input, compilation and execution :

pi@raspberrypi:/tmp $ gcc -Wall -Werror -pedantic a.c -g
pi@raspberrypi:/tmp $ ./a.out
invalid input file
LEANN RIMES : WHAT A WONDERFUL WORLD / 4628
BLUE CHEER : WHAT DOESNT KILL YOU / 9664
pi@raspberrypi:/tmp $ 

As you see the file is invalid, the reason is the author "WITHIN TEMPTATION & KEITH CAPUTO" more the newline is too long to be saved in 20 characters, this is why you always need to check what happens and never suppose all is ok : in your initial code out of your other problems fscanf write out of the items with an undefined behavior. To read for instance up to 20 characters including the null character in a string with (f/s)scanf use the format "%20s"

If I resize the field author to 40 all is ok :

pi@raspberrypi:/tmp $ gcc -Wall -Werror -pedantic a.c -g
pi@raspberrypi:/tmp $ ./a.out
LEANN RIMES : WHAT A WONDERFUL WORLD / 4628
BLUE CHEER : WHAT DOESNT KILL YOU / 9664
WITHIN TEMPTATION & KEITH CAPUTO : WHAT HAVE YOU DONE / 3226
WITHIN TEMPATION : WHAT HAVE YOU DONE / 8093
KOKO TAYLOR : WHAT IT TAKES (THE CHESS YEARS) / 7160
DOOBIE BROTHERS : WHAT WERE ONCE VICES ARE NOW HABITS / 2972
LIL'ED & THE BLUES IMPERIALS : WHAT YOU SEE IS WHAT YOU GET / 9443
VARIOUS ARTISTS : WHAT'S SHAKIN / 4473
pi@raspberrypi:/tmp $ 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM