7

I noticed that in C, my boolean variable somehow gets changed in a way I don't understand.

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

int main(void) {
   bool x, y;

   printf("x: ");
   scanf("%d", &x);

   printf("x is %d\n", x);

   printf("y: ");
   scanf("%d", &y);

   printf("x is %d\n", x);
   printf("y is %d\n", y);

   return 0;
}

If I input a value of 1 for x and any value for y (1 in this example):

x: 1
x is 1
y: 1
x is 0
y is 1

at the end, y outputs the correct original value, but x magically changes to 0 in between!

This is not a problem when the input for x is 0 since the outputs for both x and y are their respective original values as expected.

Please explain what is going on!

msc
  • 33,420
  • 29
  • 119
  • 214

7 Answers7

8

You are passing address of boolean variable to scanf() which expects variable of type int*. This will invoke undefined behavior and you may get wrong results or even crash.

To solve this problem, use temporary int to store scanning of an boolean value (as int), and after that store it to boolean variable.

Demo

bool x, y;
int tmp;

printf("x: ");
scanf("%d", &tmp);
x = tmp;

On the other hand, printing boolan variable is different story, where boolean value is protomoted to int without any problems and printed correctly.

kocica
  • 6,412
  • 2
  • 14
  • 35
7

OK, two points here.

  1. The size of bool is implementation-defined.
  2. There is no format specifier defined for bool type in the standard.

So, while scanning the value, passing the address of a bool as the argument for %d is bad see note as the supplied type is not the same as expected type.

You can use a intermediate integer, scan the value into that and (after validation or transformation to true and false MACROs) assign the result back to bool type variable.

For printing, however, because of default argument promotion, a bool can be a candidate for the argument for %d without a problem.


Note:

%d with *scanf() expects the argument to be an int *, instead supplying a bool* will cause undefined behavior.

Related, quoting from chapter §7.21.6.2, Paragraph 10

[....] Unless assignment suppression was indicated by a *, the result of the conversion is placed in the object pointed to by the first argument following the format argument that has not already received a conversion result. If this object does not have an appropriate type, or if the result of the conversion cannot be represented in the object, the behavior is undefined.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
7

A bool isn't an int. Reading it with the %d format specifier for an int is undefined behavior.

Per 7.21.6.2 The fscanf function, paragraph 13 of the C standard:

If a conversion specification is invalid, the behavior is undefined.

Note that paragraph 9 of 7.21.6.1 The fprintf function states:

If a conversion specification is invalid, the behavior is undefined. If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

But that's for fprintf(), not fscanf(). Format specifiers are much more stringent for the scanf() functions as there will be no argument promotion that allows a format such as %d to "work" for a char or bool, which get promoted to int for a printf() call. The scanf() functions are passed the address of the argument, and if what the address refers to is the wrong size from what is expected per the format specifier, undefined behavior will result - such as unexplained changes to another variable.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • 2
    This is **wrong**. The conversion specification is __not invalid__ here. see [this](https://stackoverflow.com/q/45588405/2173917) – Sourav Ghosh Aug 31 '17 at 11:05
  • @SouravGhosh How do you know? – Andrew Henle Aug 31 '17 at 11:07
  • @SouravGhosh: but isn't this exactly what you are saying in your answer ??? – Paul R Aug 31 '17 at 11:07
  • 1
    @PaulR no sir, the conversion specification is __fine__, it's the argument that's mismatching there. – Sourav Ghosh Aug 31 '17 at 11:08
  • 1
    @SouravGhosh Huh? Obviously people mean "the conversion specifier is wrong for the argument provided" – M.M Aug 31 '17 at 11:09
  • @SouravGhosh: the point is that the conversion specification and the type of the parameter don't match. It's a perfectly good answer if you read it carefully. – Paul R Aug 31 '17 at 11:10
  • @SouravGhosh *no sir, the conversion specification is fine, it's the argument that's mismatching there.* How can the conversion specification be "fine" if it doesn't match the argument? That's the **definition** of an invalid conversion specification: "under control of the string pointed to by format that specifies the admissible input sequences and how they are to be converted for assignment, using subsequent arguments as pointers to the objects to receive the converted input." – Andrew Henle Aug 31 '17 at 11:11
  • @PaulR wrong and invalid are used in different context, as I read it. Also, i have linked a question of mine in this context. – Sourav Ghosh Aug 31 '17 at 11:11
  • @user694733 That's an explanatory footnote. And [in this copy of the C standard](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf), footnote 282 reads "282) See ‘‘future library directions’’ (7.31.11)." – Andrew Henle Aug 31 '17 at 11:15
  • First off I just started learning C two days ago, so I don't understand 90% of the things in that PDF. Also, these comments are just making me more confused as to what is cause of the problem. For now it sounds like an issue of semantics to me. – Naturally Upbeat Student Aug 31 '17 at 11:17
  • @user694733 OK, you're quoting the `fprintf()` section, paragraph 9. That following sentence isn't in paragraph 13 of the `fscanf()` section that I quoted. I'd think the `fscanf()` section is more appropriate, especially since `fprintf()` format specifiers *de facto* allow a lot of "slop" because of argument promotion, which won't help with any of the `scanf()` functions. – Andrew Henle Aug 31 '17 at 11:22
  • @AndrewHenle I feel like an idiot now :P Sorry. Deleteting comments. – user694733 Aug 31 '17 at 11:24
  • @user694733 That was useful, and probably explains some of the *other* comments - and misguided downvotes, as there is *no* reference to the argument not being the correct type for the conversion specification in the ***fscanf*** section of the standard. – Andrew Henle Aug 31 '17 at 11:28
  • 1
    Reading the `fscanf` docs, I still think there may be an issue. I think you should've quoted paragraph 10, specifically *"... If this object does not have an appropriate type, or ..., the behavior is undefined"*. I believe paragraph 13 covers only malformed conversion specifier syntax (like non-existent `%k`). – user694733 Aug 31 '17 at 11:36
  • @user694733 I'm going to have to re-read paragraph 10 now. :-/ I had viewed that as referring to the *processing* of the input data but at the end it does move on to addressing the storing of the conversion results. – Andrew Henle Aug 31 '17 at 12:16
  • Good that this answer emphasizes `%d` matches not a `bool` or `int`, but an `int *`. – chux - Reinstate Monica Aug 31 '17 at 14:19
5

bool is a different data type then int, and it is likely to be a single byte.

scanf is not type-safe function, you are telling it with the %d conversion specifier that expects pointer to an int, and scanf has no way to know that you have passed pointer to bool instead of pointer to an int. Then, you will get undefined behaviour.

clang compiler generated warning:

source_file.c:8:16: warning: format specifies type 'int *' but the argument has type 'bool *' [-Wformat]
   scanf("%d", &x);
          ~~   ^~
source_file.c:13:16: warning: format specifies type 'int *' but the argument has type 'bool *' [-Wformat]
   scanf("%d", &y);
          ~~   ^~
msc
  • 33,420
  • 29
  • 119
  • 214
3

All the other answers are technically correct, but don't really help you.

Your main problem is not x nor y, but the fact that you are unable to debug the most trivial programming problems yourself. This is easy to fix - enable compiler warnings (if using gcc, add -Wall option). Compiler will inform you about a mistake like this, and many-many more. No need to come to stackoverflow every time :)

Erki Aring
  • 2,032
  • 13
  • 15
0

Consider the following program

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

int main(void) {
  int z, s;
  printf("int  z: %p\nint  s: %p\n", &z, &s);

  bool x, y;
  printf("bool x: %p\nbool y: %p\n", &x, &y);

  return 0;
}

The output:

int  z: 0028FF3C
int  s: 0028FF38
bool x: 0028FF37
bool y: 0028FF36

Now, between z and s there is a 4 byte difference which is the size of an int in my case.

Between x and y there is only 1 byte difference.

Now with this line:

scanf("%d", &y);

You are telling with the %d to scanf that you want require a 4 byte long input, an int.

This int will be stored in case of y from the address 0028FF36 and with an input of 0x0001 the second byte which is a 0 will be stored at 0028FF37. And that is the address of x.

You can check it with another input value. For example if you give 65535 to y which is 0xFFFF then x will be 0xF, which is 255 decimal.

Bence Kaulics
  • 7,066
  • 7
  • 33
  • 63
0

or maybe (if input string contains true or is 1 will return 1 - otherwise 0)

int getBool(char *message)
{
    char input[20];
    printf("%s", message);
    fgets(input, 20, stdin);
    return !!strstr(strlwr(input), "true") || strtol(input, NULL, 10);
}
0___________
  • 60,014
  • 4
  • 34
  • 74