Why did Win32 define BOOL as a signed int instead of an unsigned int?


Igor Levicki wants somebody from Microsoft to explain why BOOL was defined as a signed int instead of an unsigned int.

You don't need to work for Microsoft to figure this out. All the information you need is publically available.

Quoting from K&R Classic, which was the operative C standards document at the time Windows was being developed:

7.6 Relational Operators

The [relational operators] all yield 0 if the specified relation is false and 1 if it is true. The type of the result is int.

Win32 defined BOOL as synonymous with int because Brian and Dennis said so. If you want to know why Brian and Dennis decided to have the result of relational operators be signed instead of unsigned, you'll have to ask them.

Comments (48)
  1. Random832 says:

    "You don't need to work for Microsoft to figure this out. All the information you need is publically available." Well, except for the information on why it was important for BOOL to be the same type as relational operators return. C++ (and for that matter, C99 with its _Bool type) didn't make the same decision in the same situation.

    [I thought the reason was obvious. You want to be able to say "return a == b;" and not "return (BOOL)(a == b);". -Raymond]
  2. John says:

    "why it was important for BOOL to be the same type as relational operators return" is outside the scope of this discussion.  It was defined that way in the standard and Microsoft followed the standard.

  3. MikMik says:

    And then we have things like functions declared as BOOL that return 0 if false and any other thing (not only 1) if true, e.g. ::IsPathDirectory, which returns 16 if the path is a directory (though the docs say, or said, it returns TRUE).

    It would have been the same with unsigned int, but if the reason is they were following the standards, they could have followed them not only with type declarations, but also with how functions work.

  4. laonianren says:

    random832 said: "why it was important for BOOL to be the same type as relational operators return"

    Because if they weren't the same you would have two boolean types, "Windows booleans" and "C booleans", which would have been very confusing.

    @MikMik: returning zero/non-zero (as opposed to 0/1) is consistent with the C standard library (e.g. the isdigit function).

  5. Joel Dillon says:

    I might be misremembering, but I don't think K&R specified whether int was signed or unsigned – it could be either (much as char can be signed or unsigned). Otherwise the 'signed' keyword would be a bit of a waste. So technically Microsoft did decide that BOOL was signed because they decided that int was signed; not that that makes their decision invalid.

  6. Adam Rosenfield says:

    @laonianren: Oh there's more than two boolean types blogs.msdn.com/…/329884.aspx .

  7. Random832 says:

    ""why it was important for BOOL to be the same type as relational operators return" is outside the scope of this discussion.  It was defined that way in the standard and Microsoft followed the standard."

    It's not only not outside the scope of this discussion, it is the entire scope of this discussion. The standard says nothing about "BOOL". It only says that relational operators return int. BOOL could still be an unsigned type not returned by relational operators and follow the standard. That was my point. Both C++ and C99 made similarly named types [bool and _Bool respectively] which are not int, so it's not like it would only be Microsoft doing that [they might not even be the first, depending on the timing of C++]

  8. Alex Grigoriev says:

    @Joel Dillon:

    'int' is always the same as 'signed int'. Period. It's not implementation specific, or a matter of choice otherwise. Unlike 'char'.

  9. Henning Makholm says:

    Joel Dillon: You're misremembering. It has never been possible for plain int to be unsigned; too many core C library functions use negative values as exceptional return values.

  10. S says:

    @Random832

    But C99 and C++ also changed the type that relational operators return. C++ changed it to bool, I don't know what C99 does because I don't know anything about it. Microsoft didn't have that luxury because they weren't redefining the language, so if they wanted BOOL to be interchangeable with relational operators they had to use int. C99 and C++ didn't exist, or at least weren't standardised, at the time this decision was made.

  11. Adam Rosenfield says:

    Random832: "Both C++ and C99 made similarly named types [bool and _Bool respectively] which are not int, so it's not like it would only be Microsoft doing that [they might not even be the first, depending on the timing of C++]"

    Except Microsoft isn't creating a new language in this case or changing what the relational operators return.  If you had this function:

    BOOL AreEqual(int x, int y) { return x == y; }

    You don't want the compiler to give you a spurious warning about converting a signed integer to an unsigned integer.

  12. K says:

    And we can be happy about that. The more int and the less uint we use, the safer the code becomes. The advantage of using uints for array length pales to the problems that happen when you write "if (array.length -n > m)" and fail to realize that in case of uints, this will often result in suboptimal results. I am using an in-house library which does it, and it's annoying.

    I am sure people can come up with more issues that happen when you accidentally use uints, but there are few that happen when you accidentally use signed ints instead of uints.

  13. Surely it's a simple as a 16-bit (at the time) int being the most-regularly used type (and closet to register size), and sign is irrelevant since the machine code produce is just a compare to 0, so any bit set will produce the right result. Since there was no special reason to make it something other than the most common primitive type, they didn't.

  14. I was talking about K+R more than Win32 btw.

  15. S says:

    @K

    The counter example here is for BOOL, if someone decides to "optimize" a BOOL by turning it into "BOOL fSomething : 1" signed makes it less safe. This is because fSomething can only have the values FALSE and -1 (assuming 2's complement), it is not possible for it to be TRUE. So if somewhere in the code you have if (fSomething == TRUE), signed numbers just made your life more difficult.

  16. JS Bangs says:

    @s, anybody writing if (fSomething == TRUE) rather than just if (fSomething) needs to be beaten with a cluebat.

  17. JS Bangs says:

    @s, anybody writing if (fSomething == TRUE) rather than just if (fSomething) needs to be beaten with a cluebat.

  18. JM says:

    "but there are few that happen when you accidentally use signed ints instead of uints."

    Don't worry, C has plenty of undefined behavior to go around. See blog.regehr.org/…/226 for all sorts of fun examples of things going wrong when you use signed types instead of unsigned types. Problems with undefined signed operations not doing what you want can be much more pernicious than problems with unsigned types — at least there the behavior is defined, if unwanted.

    @S:

    That's a problem with how you define and use TRUE, not signedness. Writing "if (fSomething == TRUE)" is just wrong if you intend to mimick boolean semantics. In C, any nonzero value is true, so any comparison with a particular value standing in for true is wrong. In that respect, it doesn't matter how you define TRUE — as long as you're not perverse and define it to be 0. Alternatively, if you decide the test is correct, then the "optimization" is wrong — it should have used "TRUE", not "1", as the only legal values for BOOL would be TRUE and FALSE, not arbitrary integers. (MSDN helpfully says the values of BOOL "should" be "TRUE" and "FALSE" only, but of course you'd be very foolish to count on this as a consumer.)

  19. DrkMatter says:

    Asides from considerations of compiler warnings, isn't this whole discussion moot, considering you're not supposed to do anything with a boolean type except compare it for equality with 0?

  20. Evan says:

    @S:

    I'm appalled that none of the three compliers I tried (VS 2008 with /W4, GCC with -W -Wall, and Comeau's online front end with -a -r) warns about that, because the reality is even worse than what you say: the only value that the standard guarantees 'fSomething' can hold in that example is 0. You can't even rely on -1 without being technically non-portable.

    Furthermore, if I add an actual comparison to 1, only GCC warns about it, and only with the -W flag.

    struct Foo {

     int field : 1;

    };

    void fun() {

     Foo f;

     if (f.field == 1);    // gcc says "comparison is always false due to limited range of data type"

    }

    It's a pity MSVC doesn't give more diagonstics on that one.

  21. S says:

    @JM, @JS Bangs

    Sure – I know that, and you know that. The problem is that not everyone knows that and it is hard to find those tests in a large code base. I wasn't in anyway suggesting that either the "optimization" or the test were correct, but that they were a possible failure mode of signed integers that I have seen in code I didn't write.

  22. Cesar says:

    @K:

    Using signed integers for length can be a source of subtle security problems.

    // This came from the network

    struct mypacket {

     int length;

     char stuff[42];

    };

    void myfunction(struct mypacket *pkt)

    {

     char buf[42];

     if (pkt->length > 42)

       return;

     memcpy(buf, pkt->stuff, pkt->length);

     // …

    }

    Looks good, right? We are testing that the length is not bigger than the destination buffer. Now, think about what happens if the length is NEGATIVE…

    If you are using signed integers, you ALWAYS have to check for both less than zero and greater than the maximum length. If you are using unsigned integers, due to the way two's complement works, the test for "less than zero" is implicit within any test for "greater than the maximum length".

  23. Joseph Koss says:

    -1 is actually a quite common value for TRUE among the various other languages over the years, many of which do/did not differentiate between logical and bitwise boolean operations (using bitwise for both!)

    The justification is simple, beautiful, and powerful:

    TRUE should equal NOT FALSE

    Thus if FALSE is the signed integer 0, then TRUE is by definition -1.

  24. Arlie says:

    "@s, anybody writing if (fSomething == TRUE) rather than just if (fSomething) needs to be beaten with a cluebat."

    If you saw exactly that code, you might think the author was an idiot.  But that exact situation comes up, legitimately, in the real world.  Consider this:

    // in header file foo.h

    #define OPTION_FOO_IS_ENABLED TRUE

    // in source file bar.c

    if (fSomething == OPTION_FOO_IS_ENABLED) { … }

    That code looks much more reasonable, even though it suffers from the same problem.  To eliminate the possibility of ambiguous boolean comparisons, you would have to do something like this:

    // in source file bar.c

    if (OPTION_FOO_IS_ENABLED ? (fSomething) : (!fSomething)) { … }

    Which just looks bizarre.  I've also seen it done this way:

    if (!!OPTION_FOO_IS_ENABLED == !!fSomething) { … }

  25. Jeremy says:

    I've come across some pretty bizarre code that looks like this:

    BOOL MyPathFileExists(const MyStringType & pth)

    {

       return !!PathFileExists(pth.c_string());

    }

    At one time, that double-not idiom was pretty common in our codebase.  Presumably because someone liked explicit comparisons with TRUE.

  26. Alex Grigoriev says:

    @Arlie:

    Your code examples are so bad, that I don't know where to start.

  27. dave says:

    anybody writing if (fSomething == TRUE) rather than just if (fSomething) needs to be beaten with a cluebat.

    Unless of course they write

      if (((fSomething == TRUE) == TRUE) == TRUE)

    in which case they can be commended for wit and sarcasm.

  28. Burak KALAYCI says:

    > Win32 defined BOOL as synonymous with int because Brian and Dennis said so.

    This makes sense only if you implicitly accept that using C was the only choice for Win32 at the time. Did Brian and Dennis also told Microsoft to write Win32 (or Win16) using C?

    More importantly, if this is the real answer to Igor's question, then how will you be able to justify any Microsoft act that does not conform to standards?

    ["Because Brian and Dennis said so, and there was no compelling reason to disagree with them." -Raymond]
  29. Cheong says:

    @Burak: At that time, most of the compilers has support to link libraries using C calling convention, making code generation routine align with C standard can reduce overhead of these calls. (These were the days when reducing execution cycle consumptions does matters. Although BOOL defination certainly isn't part of the convertion, having everyone agrees on this makes function calls simpler.)

  30. Toddsa says:

    It is kind of funny how people disagree and argue so adamantly with even innocuous implementations of code. In reality most implementations are because that is how the developer at the time designed it and most designs are not done by a community, nor should they be.

  31. Gabe says:

    Cheong: Win16 functions all used PASCAL calling convention (to save a cycle or two on every call maybe?), with the exception of variadic functions that are only possible with the C calling convention.

  32. 640k says:

    A C enum with TRUE and FALSE would solve the fTrueVar!=TRUE problem.

  33. Leo Davidson says:

    @640k:

    I'm not sure that would have solved anything as I don't think C even shows a warning if you assign a random int to an enum type. This compiles fine in a .c file (and C++ is not relevant as it did not exist back then, but even if it did you would introduce the need to cast every comparison function to your enum in that case):

    typedef enum

    {

    MYFALSE,
    
    MYTRUE
    

    } MYBOOL;

    MYBOOL test()

    {

    return 42;
    

    }

  34. D says:

    Half the world doesn't understand boolean testing anyway, the number of times I see this in code:

     BOOL booleanValue;

     char *pointer;

     if (booleanValue == FALSE)

     if (!pointer)

    which ought to be

     if (!booleanValue)

     if (pointer == NULL)

  35. dave says:

    re:

    if (!pointer)

    "should be"

    if (pointer == NULL)

    Half the world doesn't understand language specification anyway.  Just because you think the programmer should manually convert his pointer operand to bool, rather than letting the compiler do it, it doesn't follow that the rest of us agree.

    Confession: I don't have any relevant C spec to hand, so I'm relying on the C++ spec being similar, but it may not be: but ISO/IEC 14882:2003(E) says, in section 5.3.1 paragraph 8, "the operand of the logical negation operator ! is implictly converted to bool".

    The goal of 'style' is surely to write in a way that is clear to readers; I find it difficult to believe that there's a competent C programmer who does not understand the 'if (!pointer)' idiom.

    Me, I like appropriately-terse coding.  Don't be long-winded for no good reason.

  36. Alex Cohn says:

    @Burak: for languages other than C the question of signed/unsigned is, generally speaking, irrelevant. This strengthens the point of BOOL actually spaning two values : FALSE or not FALSE.

  37. Rich the Engineer says:

    My goodness, all this commotion over a two-valued construct.  Good thing we didn't start with ternary logic.

  38. !bool says:

    @Rich: That's what nullable bool is for :)

  39. Cesar says:

    @Joseph Koss:

    IIRC, C (and C++) does not define the evaluation order for parameters. The compiler is free to evaluate them in any order it wants (which might not be neither left-to-right nor right-to-left).

  40. acq says:

    From the C++ standard, 4.12 Boolean conversions [conv.bool]:

    "A zero value, null pointer value, or null member pointer value is converted to false any other value is converted to true."

    From C standard, 6.5.3.3 Unary arithmetic operators

    "The expression !E is equivalent to (0==E)."

    That means to me that both "if ( ptr )" and "if ( !ptr )" are valid idioms in C and C++.

  41. Mike Dimmick says:

    @Toddsa: It's the bike shed problem. Everyone knows how to build a bike shed (or at least can reasonably conceive of how to build one) and therefore, if you're looking for approval to build one, there are endless arguments about the fine details.

    Something much more complicated is often easier to get past approvals because the board approving it usually *doesn't* know what you're talking about.

  42. Joseph Koss says:

    @Gabe:

    Win16 used PASCAL to save 3 bytes per call (see Raymond Chen's link below.)

    Win32 of course uses STDCALL, often referred to as "a hybrid of CDECL and PASCAL" but not widely known is that its essentially the BASIC (as defined by early MASM and BASCOM) calling convention renamed. It is the same as PASCAL except that, like the CDECL convention (*), the parameters are pushed right to left instead of left to right.

    Callee-cleanup really is the way to go. In addition to its space savings, its much safer from a debugging standpoint (CDECL has an annoying habit of "hiding" bugs where an incorrect number of parameters is passed to a function.) Sure, there are times when you really do need something like CDECL (printf) but with function overloading and stream operators that has thankfully become the exception and certainly no longer the rule.

    blogs.msdn.com/…/47184.aspx

    (*) I'm not sure why right to left is considered a good idea given that nearly all languages define evaluation order of parameters as occurring left to right. This method does put the first parameter lowest in memory on x86 (the stack grows downwards) but that hardly seems important enough to justify it.

  43. GregM says:

    "Sometimes I think I must be the only C (really, C++) guy out there who actually may prefer that to "if (!booleanValue)"."

    You are not alone.

  44. Michael Grier [MSFT] says:

    Re: !! to canonicalize Boolean values:

    return !!PathFileExists(pth.c_string());

    The problem with not having the !! is that while the *intent* of a BOOL returning function is clear, in practice, the PathFileExists() function may return an arbitrary value for "TRUE", perhaps indicating the kind of path if it is found to exist.

    If you don't canonicalize the value you return, returning whatever PathFileExists() happens to return becomes part of your API contract instead of TRUE vs. FALSE.

    If your software is unpopular and unsuccessful, it doesn't matter and you saved a few milliseconds avoiding typing the !!.  However if you are blessed with becoming popular and successful, you're stuck supporting the old behavior.  Forever.

  45. Evan says:

    @D: "Half the world doesn't understand boolean testing anyway, the number of times I see this in code: if (booleanValue == FALSE)"

    Sometimes I think I must be the only C (really, C++) guy out there who actually may prefer that to "if (!booleanValue)". I think part of the reason is it can be relatively easy to miss the ! if you're just scanning over the code. (I also like 'if (ptr == NULL)', probably for a similar reason. Abbreviating that to 'if (ptr)' I feel is exactly the *wrong* kind of terseness to strive for.)

  46. Cheong says:

    @Gabe: I know, but just recall that when they first developed Windows, most of the parts are written in C and assembly language.

    It just make sense to follow the convention of the programming tools you use, especially since there weren't any QC process you have today. Using only ONE defination of boolean can significantly reduce possibility of introducing bugs than having to take care of checking and conversion. (Remenber, that's the time when compilers can have bugs. If you were given the choice to choose from (even if subconsciously), you'll want to make the rules as simple as it should be.)

  47. Fred says:

    @Joseph: "nearly all languages define evaluation order of parameters as occurring left to right"… well, neither C nor C++ define that.  In C and C++, the evaluation order of parameters is undefined.  Compilers can and do evaluate them in any order, and enabling optimization can change the order.

  48. Anonymous says:

    @MikMik

    I was a bit puzzled at why it returned 16, then it dawned on me that FILE_ATTRIBUTE_DIRECTORY is 0x10.  So it's probably returning (dwAttributes & 0x10) and someone thought it was good enough to not have to add ? TRUE : FALSE after that.

Comments are closed.

Skip to main content