c99 and the lack of warnings when int operations are applied to abool

M

mathog

I just found out the hard way that the C99 "bool" type can result in
some very hard to diagnose bugs. This problem came up in the context of
a field in a structure which had been declared in a file "long ago and
far away" and which I had not seen. From the usage in the file where
the problem was found it looked like this value was an int, but it was a
bool. As it turns out at least the gcc compiler (4.4.1) gives no
warnings when bools are used in ways that are "int" like but make little
or no sense for a bool.

Example code showing some of these issues:

/* testbool.c */
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

void main(void){
bool avar;
avar=1;
(void) fprintf(stdout,"avar = %d\n",avar);
avar=2;
(void) fprintf(stdout,"avar = %d\n",avar);
avar+=2;
(void) fprintf(stdout,"avar = %d\n",avar);
avar &= 2;
(void) fprintf(stdout,"avar = %d\n",avar);
avar=2;
if(avar==2){
(void) fprintf(stdout,"avar == 2\n");
}
else {
(void) fprintf(stdout,"avar != 2\n");
}
}

# gcc -Wall -std=c99 -o testbool testbool.c
testbool.c:5: warning: return type of 'main' is not 'int'
# ./testbool
avar = 1
avar = 1
avar = 1
avar = 0
avar != 2

To my mind the only two of these statements that make sense as operators
on a bool are "=1" and "=2", both setting the bool to 1 (true), and the
second one only if

bool_variable = int_variable;

is always automatically converted to

bool_variable = (bool) int_variable;

The rest of them make little or no sense for an operation on a bool, yet
the compiler gives not a hint of a problem. For instance, there is no
point, ever, in incrementing a bool by 2 since the most it can change is
by 1. Similarly logic on any but the lowest bit is pointless since they
will never be set. Finally, the test "avar == 2" really should trigger
a warning since a bool can never be 2, and the 2 is not automatically
promoted to a bool (maybe the bool is promoted to an int?) In any case
the test is clearly a programming error since it can never be true, and
the compiler can know that without having to reference any other piece
of code. Seems to me that if this does not generate a warning then the
compiler should be doing it the other way around, consider the "2" as a
bool, thus true, and the result should be True. (Which it isn't.)

What exactly are the rules for bool such that the behavior shown above
all makes sense (and is not worthy of a compiler warning)?

Thanks,

David Mathog
 
J

James Kuyper

I just found out the hard way that the C99 "bool" type can result in
some very hard to diagnose bugs. This problem came up in the context of
a field in a structure which had been declared in a file "long ago and
far away" and which I had not seen. From the usage in the file where
the problem was found it looked like this value was an int, but it was a
bool. As it turns out at least the gcc compiler (4.4.1) gives no
warnings when bools are used in ways that are "int" like but make little
or no sense for a bool.

Example code showing some of these issues:

/* testbool.c */
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

void main(void){
bool avar;
avar=1;
(void) fprintf(stdout,"avar = %d\n",avar);
avar=2;
(void) fprintf(stdout,"avar = %d\n",avar);
avar+=2;
(void) fprintf(stdout,"avar = %d\n",avar);
avar &= 2;
(void) fprintf(stdout,"avar = %d\n",avar);
avar=2;
if(avar==2){
(void) fprintf(stdout,"avar == 2\n");
}
else {
(void) fprintf(stdout,"avar != 2\n");
}
}

# gcc -Wall -std=c99 -o testbool testbool.c
testbool.c:5: warning: return type of 'main' is not 'int'
# ./testbool
avar = 1
avar = 1
avar = 1
avar = 0
avar != 2

To my mind the only two of these statements that make sense as operators
on a bool are "=1" and "=2", both setting the bool to 1 (true), and the
second one only if

bool_variable = int_variable;

is always automatically converted to

bool_variable = (bool) int_variable;

The rest of them make little or no sense for an operation on a bool, yet
the compiler gives not a hint of a problem. For instance, there is no
point, ever, in incrementing a bool by 2 since the most it can change is
by 1. Similarly logic on any but the lowest bit is pointless since they
will never be set. Finally, the test "avar == 2" really should trigger
a warning since a bool can never be 2, and the 2 is not automatically
promoted to a bool (maybe the bool is promoted to an int?) In any case
the test is clearly a programming error since it can never be true, and
the compiler can know that without having to reference any other piece
of code. Seems to me that if this does not generate a warning then the
compiler should be doing it the other way around, consider the "2" as a
bool, thus true, and the result should be True. (Which it isn't.)

What exactly are the rules for bool such that the behavior shown above
all makes sense (and is not worthy of a compiler warning)?

There are just two special rules that apply to expressions involving
_Bool operands:
6.3.1.1p1: "The [integer conversion] rank of _Bool shall be less than
the rank of all other standard integer types."

This means, in particular, it has a rank less than that of 'int'. As a
result, operands of type _Bool are promoted to 'int' in just about every
context, before an expression is evaluated. That's why _Bool is usable
just about anywhere that an 'int' is usable.
There are exceptions, such as sizeof(avar), which is not necessarily the
same as sizeof(int), but none of those exceptions come up in your code.
6.3.1.2p1: "When any scalar value is converted to _Bool, the result is 0
if the value compares equal to 0; otherwise, the result is 1." Note that
null pointers compare equal to 0, and NaNs do not.

Everything you've seen follows from those two rules.
 
J

jacob navia

Le 23/03/12 16:40, mathog a écrit :
I just found out the hard way that the C99 "bool" type can result in
some very hard to diagnose bugs. This problem came up in the context of
a field in a structure which had been declared in a file "long ago and
far away" and which I had not seen. From the usage in the file where the
problem was found it looked like this value was an int, but it was a
bool. As it turns out at least the gcc compiler (4.4.1) gives no
warnings when bools are used in ways that are "int" like but make little
or no sense for a bool.

The rest of them make little or no sense for an operation on a bool, yet
the compiler gives not a hint of a problem. For instance, there is no
point, ever, in incrementing a bool by 2 since the most it can change is
by 1.

I think you have an important point here. Bool is a logical value, not
an arithmetic or floating point value. There is no sense in taking the
square root of a bool, nor there is any sense in dividing a bool by any
integer: the result is always zero unless the other number is 1.

Treating boolean variables as integers is an error, and I think the
standard should disallow arithmetic with booleans...
 
J

jacob navia

Le 23/03/12 23:17, Robert Wessel a écrit :
Yet the language has always allowed the results of logical or
relational operators to be used arithmetically. How do you define a
rule that continues to allow that but disallows the use of _Bools?

The rule could be simply:

Only boolean operations are allowed with bools: or, and, xor, not. If
you want to do arithmetic with bools you store that into an int (or
long double or whatever) and then you go on from there.

Or you make a cast.

There shouldn't be any *implicit* conversions.
 
I

Ian Collins

Le 23/03/12 23:17, Robert Wessel a écrit :

The rule could be simply:

Only boolean operations are allowed with bools: or, and, xor, not. If
you want to do arithmetic with bools you store that into an int (or
long double or whatever) and then you go on from there.

Or you make a cast.

There shouldn't be any *implicit* conversions.

Isn't assigning a _Bool to an int an implicit conversion?
 
J

jacob navia

Le 24/03/12 07:15, Robert Wessel a écrit :
So you're suggesting that:

d += (b!=c); /* works*/

but:

bool a = (b!=c); d+= a; /* doesn't work */

If d is an int, the integer is being modified, not the boolean, so
it is perfectly OK.

But if you write bool b=0; b += 5;

the boolean is being modified, that should be off limits or should give
you a warning.

By the way, the lcc-win warns if you write

char m = 259;

or short f = 966776655;
Since any addition to a bool is mostly nonsense, it would be OK
to warn about it.
 
J

jacob navia

Le 23/03/12 23:48, Ian Collins a écrit :
Isn't assigning a _Bool to an int an implicit conversion?

Yes it is, but I am speaking about arithmetic operations like
multiplication and division.

Those operations have no meaning with bools.


bool a,b;

a*b | 1 | 0 |
------------------
0 | 0 | 0 |
1 | 1 | 0 |

a/b is a division by zero if b is zero. If b is one,
then it is a NOP.

a/b | 1 | 0 |
------------------
0 | 0 | NAN| <<< UB
1 | 1 | NAN| <<<<UB



Mmmm substration looks like xor isn't it?

a-b | 1 | 0 |
------------------
0 | 1 | 0 |
1 | 0 | 1 |

Addition:

a+b | 1 | 0 |
------------------
0 | 1 | 0 |
1 | 1 | 1 |

This means that those operations with booleans are with VERY high
probability ERRORS.

And if you substitute in the truth tables integer values (ANY integer
values) the results are equally weird. There is no point in doing those
operations and they are simply errors.
 
W

Willem

jacob navia wrote:
) Yes it is, but I am speaking about arithmetic operations like
) multiplication and division.
)
) Those operations have no meaning with bools.
)
)
) bool a,b;
)
) a*b | 1 | 0 |
) ------------------
) 0 | 0 | 0 |
) 1 | 1 | 0 |

In boolean arithmetic, multiplication is used as 'and'.

) a/b is a division by zero if b is zero. If b is one,
) then it is a NOP.
)
) a/b | 1 | 0 |
) ------------------
) 0 | 0 | NAN| <<< UB
) 1 | 1 | NAN| <<<<UB
)
)
)
) Mmmm substration looks like xor isn't it?
)
) a-b | 1 | 0 |
) ------------------
) 0 | 1 | 0 |
) 1 | 0 | 1 |

Hmm, 'xor' is usually denoted by a plus with a circle around it.

) Addition:
)
) a+b | 1 | 0 |
) ------------------
) 0 | 1 | 0 |
) 1 | 1 | 1 |

And addition is used as 'or'.

) This means that those operations with booleans are with VERY high
) probability ERRORS.

No, at least addition and multiplication are commonplace for boolean
algebra in mathematics.


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT
 
B

BartC

Willem said:
jacob navia wrote:
) Yes it is, but I am speaking about arithmetic operations like
) multiplication and division.
)
) Those operations have no meaning with bools.
)
)
) bool a,b;
)
) a*b | 1 | 0 |
) ------------------
) 0 | 0 | 0 |
) 1 | 1 | 0 |

In boolean arithmetic, multiplication is used as 'and'. ....
And addition is used as 'or'.

Maybe. But 'and' and 'or' are also available, either as symbols (&& & || |)
or words.

And the symbol forms let you choose whether to use short-circuit evaluation
(&& ||) or not (& |).

Remember C also has small sets, in the form of integers (typically of 32 and
64 bits). You can use bitwise operations (& |) on these sets, but trying to
use * and + to do the same will result in something entirely different!

So it's best to apply the same thinking to individual bools.

(What *would* be useful would be int*bool, bool*double, etc. So A*bool is
either A, or zero. But a simple cast to int will achieve the same.)
) Mmmm substration looks like xor isn't it?
)
) a-b | 1 | 0 |
) ------------------
) 0 | 1 | 0 |
) 1 | 0 | 1 |

Hmm, 'xor' is usually denoted by a plus with a circle around it.

C has '^' (as well as 'xor'). However, subtraction is another integer
operation that makes little sense for sets. A more useful notion of
subtract, applied to individual bools, might be:

A B A-B
--------
0 0 0
0 1 0
1 0 1
1 1 0

In an actual set, this would subtract those elements of B that also exist in
A. (This is equivalent to ((A or B) xor B).) A normal subtraction won't
work. Another reason to keep bool operations distinct.
 
B

Ben Bacarisse

Gareth Owen said:
Yes, but does the mathematical definition fit the 'C' one?

If we think of boolean's as lying in Z_2 (i.e. integers mod 2) we have

1+1 (mod 2) = 2(mod 2) = 0.

With 'C' we have true + true => 1 + 1 => 2 => true.

That's what is called a Boolean ring, and is not what most people
consider to be Boolean algebra. I am not saying that it's unreasonable
to assume this extension, but it is somewhat arbitrary. You could just
treat + and * as analogs of OR and AND.
C boolean arithmetic is not Z_2 arithmetic.

The discussion has been thrown off course a little by Jacob's assertion
that C should not do arithmetic on bool values. In a very real sense
(if not a purely literal one) C does *not* do arithmetic on bool
values. All of the operators mentioned (including & and |) operate on
int values and produce int results. This means that it's not really
sensible to talk about "C Boolean arithmetic" without clarifying what is
meant. I think most posters have interpreted this to be:

(_Bool)(a OP b)

with OP being one of the arithmetic operators. With this meaning, C's
Boolean arithmetic makes perfect sense. a OP b on its own does not, but
then it's not an expression of type _Bool anyway.

Jacob later clarified that it's the automatic conversions that are the
problem, but he does not say what to do instead. Lots of rules would
have to change just to permit

_Bool a, b;
/* ... */
if (a | b) ...

to mean something. Currently that's given a meaning by the "usual
arithmetic conversions".
There's no point appealing to the commonplace in mathematics, if
applying that mathematical logic in C programs will result in incorrect
answers.

And if we want boolean logic operators, we've already got them in
~, &, | and ^

Small point: I think looks '!' is better than '~' in this list. '~'
produces wild int values that '!' does not.
 
B

Ben Bacarisse

BartC said:
(What *would* be useful would be int*bool, bool*double, etc. So A*bool is
either A, or zero. But a simple cast to int will achieve the same.)

The "would" suggests that this is not currently possible, but it is (and
no casts are needed).
 
B

Ben Bacarisse

Lowell Gilbert said:
It's possible if you're sure the bool only holds zero or one.

In practice, it is frequently safer to use
(bool ? A : 0)
which is only necessary because the bool does (in fact) typically have
the semantics of a larger type of integer

Are we talking about the same thing? C's standard bool (a macro that
expands to _Bool) does not have the semantics of a larger integer type.
 
J

James Kuyper

Are we talking about the same thing? C's standard bool (a macro that
expands to _Bool) does not have the semantics of a larger integer type.

It implicitly converts to int in most contexts, including all of the
contexts that have been the subject of this thread. I think that's what
Lowell was referring to. The only specifically boolean semantics it has
is on conversion to _Bool.
 
B

Ben Bacarisse

James Kuyper said:
It implicitly converts to int in most contexts, including all of the
contexts that have been the subject of this thread. I think that's what
Lowell was referring to. The only specifically boolean semantics it has
is on conversion to _Bool.

This: "it's possible if you're sure the bool only holds zero or one" and
"bool [has] the semantics of a larger type of integer" only "typically"
rather than always, suggests that is not what was meant.

I think he was talking about a situation where you don't know whether
you have C99 bools or a pre-C99 implementation of a bool type and, in
that context, he makes a good point, but that's not what Jacob was
talking about and this not what I was commenting on.
 
M

mathog

jacob said:
I think you have an important point here. Bool is a logical value, not
an arithmetic or floating point value. There is no sense in taking the
square root of a bool, nor there is any sense in dividing a bool by any
integer: the result is always zero unless the other number is 1.

My point exactly, even if a bool in some implementation is stored more
or less the same way as an integer type, none of the integer operations
should be applicable to it. The problem is clearer if one thinks of the
values of the bool as "True" and "False", as opposed to 0 or 1. None of
these have any meaning:

True++
True+10
True/True
True+False
etc.

Code that performed those operations is broken. Yet all of those are
accepted without a hiccup at present. This is different from the use of
an int as a logical value, where, since in some contexts the variable
might be used as an integer, and others as a logical value, all of the
above are OK.

Consider even a simple assignment:

bool b;
int i=25;
b=i;

is actually:

b= (i ? True : False);

and is not actually a direct assignment of the value of i to b. In
order to avoid difficult to find programming mistakes the explicit cast
should be required:

b = (bool) i;

and also going the other way

i = (int) b;

This would let the compiler do its job of finding suspect code. If the
standard doesn't require it would still be a very useful compiler switch
as something like:

cc --req_cast_bool

Does the standard say that these situations cannot generate a warning???
If not, then they should, at least optionally, as for instance if one
used gcc's -Wall.

Regards,

David Mathog
 
K

Keith Thompson

mathog said:
My point exactly, even if a bool in some implementation is stored more
or less the same way as an integer type, none of the integer operations
should be applicable to it. The problem is clearer if one thinks of the
values of the bool as "True" and "False", as opposed to 0 or 1. None of
these have any meaning:

True++
True+10
True/True
True+False
etc.

Code that performed those operations is broken. Yet all of those are
accepted without a hiccup at present. This is different from the use of
an int as a logical value, where, since in some contexts the variable
might be used as an integer, and others as a logical value, all of the
above are OK.

Consider even a simple assignment:

bool b;
int i=25;
b=i;

is actually:

b= (i ? True : False);

and is not actually a direct assignment of the value of i to b. In
order to avoid difficult to find programming mistakes the explicit cast
should be required:

b = (bool) i;

and also going the other way

i = (int) b;

This would let the compiler do its job of finding suspect code. If the
standard doesn't require it would still be a very useful compiler switch
as something like:

cc --req_cast_bool

Does the standard say that these situations cannot generate a warning???
If not, then they should, at least optionally, as for instance if one
used gcc's -Wall.

The standard *never* forbids warnings for anything. It specifies
constructs for which an implementation must issue a diagnostic,
which may be either a warning or a fatal error, and it specifies
exactly one case where a translation unit must be rejected (#error),
but compilers can issue any additional warnings they like.

There are languages in which bool, or Boolean, is a completely
seperate type. In Ada, for example, it's defined as an enumeration
type with some special properties (type Boolean is (False, True);),
and Ada enumeration types in general can't be mixed with integer
types. For example, there's no predefined addition operator for
Boolean, or for any other enumeration type.

C *currently* defines _Bool as an unsigned integer type that can
hold the values 0 and 1. There are some special rules regarding
conversions from other scalar types to _Bool, but you can do the
same implicit conversions between _Bool and any other arithmetic
type as you can for char, int, and so forth.

Personally, I like the Ada approach better. But I'm not sure it
would have been practical to use the same approach in C. It would
have required creating a whole new class of type, with _Bool as its
only member. If it's a scalar type, you'd have to add special-case
restrictions to forbid certain implicit conversions; otherwise
"_Bool b = 0; b += 2;" would be perfectly legal (as it is now).
It's almost inevitable that some of those restrictions would prevent
programmers from doing some perfectly sensible things.

If you don't allow 0 and 1 to be assigned to a _Bool object,
requiring literal false and true instead, then you have an elementary
type for which 0 is not a valid value. Currently, this:

some_type obj = { 0 };

is valid for any object type; would it become invalid if some_type
is a structure one of whose members is a structure containing a
_Bool member? Or would you have to permit 0 and 1 to be boolean
constants, just as 0 is a pointer constant?

What would be the required type of the condition in an if statement?
Forbidding integer, floating-point, and pointer expressions would
break most existing C programs. Obviously we need to permit _Bool
conditions. Is that a special case? Do we permit an implicit
conversion from any scalar type to _Bool? Does that conversion
occur everywhere (as it does now), or only in an expression used
as a condition?

I'm not saying that it would have been impossible to add a boolean
type that behaves as suggested, but I think it would have required
a lot more work to define it consistently, and I rather doubt that
the C committee (which consists of volunteers, remember) could have
afforded the time to do it. They might have considered it if there
were existing practice, or if someone had submitted a proposal,
but as far as I know nobody did.

And I suggest that it's too late to fix it now without breaking
existing code. There is existing code that applies the ++ operator
to boolean objects, expecting them to be set to a true value (that
goes back before C99, when the "boolean" object was probably an int).

If I were designing a C-like language from scratch today, I'd
probably make bool a distinct type without implicit conversions, and
require "if (ptr != NULL)" rather than "if (ptr)", and "if (n != 0)"
rather than "if (n)". But we no longer have the luxury of doing that
-- and I'm sure a lot of people wouldn't like that approach anyway.
 
T

Tim Rentsch

jacob navia said:
Le 23/03/12 23:48, Ian Collins a @C3{A9}crit :
Isn't assigning a _Bool to an int an implicit conversion?

Yes it is, but I am speaking about arithmetic operations like
multiplication and division.

Those operations have no meaning with bools. [snip elaboration]

The problem is in thinking of bool (ie, _Bool) as some sort of
"logical" type. It isn't. Rather it is an integer type designed
to hold the value of C expression used to do selection (eg, with ?:
or &&). Because the relational operators and so forth return 'int'
results (with value 0 or 1), they often are used in expressions
like other 'int' values (eg, by adding, multiplying, etc). Since
bool (aka _Bool) is meant hold the result values of such operators,
it is natural that _Bool should behave the same way as an operator
generating said value, that is, as holding a regular integer value.
I suppose some people may not like that, but that's how C is, and
the semantics for _Bool reasonably reflects the purpose for which
it was created.
 
J

James Kuyper

On 03/27/2012 12:53 PM, mathog wrote:
....
My point exactly, even if a bool in some implementation is stored more
or less the same way as an integer type, none of the integer operations
should be applicable to it. The problem is clearer if one thinks of the
values of the bool as "True" and "False", as opposed to 0 or 1. None of
these have any meaning:

True++
True+10
True/True
True+False
etc.

Code that performed those operations is broken. Yet all of those are
accepted without a hiccup at present. This is different from the use of
an int as a logical value, where, since in some contexts the variable
might be used as an integer, and others as a logical value, all of the
above are OK.

Consider even a simple assignment:

bool b;
int i=25;
b=i;

is actually:

b= (i ? True : False);

and is not actually a direct assignment of the value of i to b. In
order to avoid difficult to find programming mistakes the explicit cast
should be required:

b = (bool) i;

and also going the other way

i = (int) b;

This would let the compiler do its job of finding suspect code. If the
standard doesn't require it would still be a very useful compiler switch
as something like:

cc --req_cast_bool

Does the standard say that these situations cannot generate a warning???

The standard never prohibits warnings, it only mandates diagnostic
messages. A fully conforming compiler is free to warn about anything
it's makers want it to warn about. It can warn about the fact that you
used profanity in your program - it could also warn about the fact that
you did NOT use profanity in your program.

No diagnostic is mandated for any of the cases you describe, because
_Bool values are implicitly promoted to 'int', for which all of those
operations are permitted and reasonable. Whether the implicit promotion
is reasonable is another question.
If not, then they should, at least optionally, as for instance if one
used gcc's -Wall.

You can't ask gcc to warn if a _Bool value is used in an addition
expression, because that never happens - the _Bool value is promoted to
'int' before it is added.

You can ask the makers of gcc to implement a warning for all implicit
conversions of _Bool to other types. No such conversion occurs in most
contexts where use of a boolean value actually makes sense: sizeof,
_Alignof, &&, ||, !, the left operand of ?:, simple assignment when the
destination is of _Bool type, if(), while(), the second expression of
for(), and the return statement of a function declared as returning
_Bool. The main exceptions are ==, !=, and switch(), which all perform
the integer promotions on _Bool values.
 
E

Eric Sosman

My point exactly, even if a bool in some implementation is stored more
or less the same way as an integer type, none of the integer operations
should be applicable to it.

bool expensive(), complicated(), long_winded();
switch (4*expensive() + 2*complicated() + long_winded()) {
case 0: ...
case 1: ...
case 2: ...
case 3: ...
case 4: ...
case 5: ...
case 6: ...
case 7: ...
}

I'll grant that this sort of situation doesn't arise all that often,
and I'll grant that there are other ways to write the logic (there's
usually more than one way to do something in C), but I think you're
going overboard in trying to legislate the pattern out of existence.
 
J

jacob navia

Le 26/04/12 13:57, Eric Sosman a écrit :
bool expensive(), complicated(), long_winded();

With my proposal, your expression should be

switch (4*(int)expensive() + 2*(int)complicated() + (int)long_winded())

An explicit cast makes the programmer intention clear.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,902
Latest member
Elena68X5

Latest Threads

Top