How to do file I/O with bool in C?

211 Views Asked by At

Should I treat a bool variable like an int when doing file I/O?
I mean is it correct to use the following code snippet?

bool b = true;
FILE *fp1, *fp2;
...
fprintf(fp1, "%d", b);
fscanf(fp2, "%d", &b);

I'd like to know the answer based on ISO C specifications.

2

There are 2 best solutions below

6
chux - Reinstate Monica On BEST ANSWER

Should I treat a bool variable like an int when doing file I/O?

No, not for input.
Yes, OK to use for output as the bool is converted to an int as part of being a ... argument and that int matches "%d".

I mean is it correct to use ...

Not certainly. It might work. It is undefined behavior (UB).

I'd like to know the answer based on ISO C specifications.

There is no C specified way to read a bool via fscanf() and friends.

  • bool lacks a scanf() input specifier.

  • The size of bool may differ from int. It might be the same as char or others.

  • If input like "0", "1" used and "true", "false" not needed, read in an int and convert.

bool b = true;
FILE *fp1, *fp2;
...
fprintf(fp1, "%d", b);
int tmp;
fscanf(fp2, "%d", &tmp);
b = tmp;
  • Alternatively, use a string.
char tmp[2];
fscanf(fp2, "%1[0-1]", tmp);
b = tmp[0] == '1';
  • Additional work needed to accept "t", "FalsE", ...

  • Of course the return value of scanf() should be checked.

  • I'd consider a helper function:

Rough idea.  (Not really that great, just for illustration.)

// Read `bool` from `stdin`. Return 1 (success), EOF(end-of-file) and 0 in other cases.
int scan_bool(bool *b) {
  char buf[2];
  int cnt = scanf("%1[01tfTF]", buf);
  if (cnt != 1) {
    return cnt;
  }
  *b = buf[0] == '1' || buf[0] == 't' || buf[0] == 'T';
  return 1;
}
1
chux - Reinstate Monica On

OP is not asking for an alternate, yet the following, untested*1 code, can gives ideas. It tries to follow the spirit of fscanf() and accepts "0", "1", and caseless "t", "f", "true", "false".

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

/* 
 * Read a 'bool'.
 * 
 * Read stream until:
 * * ignore leading white-space.
 * * a complete case-less match to: "0", "1", "f", "t", "false", "true",
 * * end-of-file, or input error.
 * Non-matching characters are pushed back.
 *
 * On success, assign *b as true or false (1 or 0).
 *
 * Return 1 on success,
 * EOF on input error or immediate end-of-file.
 * 0 otherwise.
 */
int fscan_bool(FILE *inf, bool *b) {
  int first_ch;
  do {
    first_ch = fgetc(inf);
  } while (isspace(first_ch));
  if (first_ch == '0' || first_ch == '1') {
    *b = (first_ch == '1');
    return 1;
  }
  if (first_ch == EOF) {
    return EOF;
  }
  int buf[5];
  buf[0] = first_ch;
  first_ch = tolower(first_ch);
  if (first_ch != 'f' && first_ch != 't') {
    ungetc(buf[0], inf);
    return 0;
  }

  bool value = (first_ch == 't');
  static const char *ft[2] = {"false", "true"};
  const char *match = ft[value];
  for (unsigned i = 1; match[i]; i++) {
    buf[i] = fgetc(inf);
    int next_ch = tolower(buf[i]);
    if ((next_ch == EOF) && !feof(inf)) { // Input error
      return EOF;
    }
    if (next_ch != match[i]) {
      do {
        // Attempt to unget all "next" characters.
        // The first attempt should work if not EOF.
        // Subsequent ones may work, may fail.
        ungetc(buf[i--], inf);
      } while (i > 0);
      break;
    }
  }
  *b = value;
  return 1;
}

*1 Turns out thorough testing is tricky.

Code review request for similar code and test harness here.