The Little Bool of Doom (2025)(blog.svgames.pl)
62 points by pocksuppet 4 hours ago | 21 comments
- kccqzy 3 hours agoExcellent debugging journey.
If I were the author, I would skip the part about using the compiler explorer and reading the assembly. When I write C code, I need to satisfy the rules of the C language. Unless I’m debugging the compiler or dealing with performance issues, my experience is that reading the generated assembler and understanding it is usually a slow way of debugging. The author eventually did compile with -fsanitize=undefined but I would honestly do that first. It prints a nice error message about the exact issue as the author has shown.
[-]- direwolf20 3 hours agoI have to disagree. If you merely want to fix the problem, you can stop as soon as you find something that's awry and whose alteration removes the problem. But don't you want to understand the problem? Don't you want to see how the compiler can reasonably generate code that says a bool variable is true and false at the same time?[-]
- kccqzy 2 hours agoIt’s about abstraction layers. Most of the time, understanding the generated assembler code isn’t useful when it comes to understanding the problem. It satisfies my curiosity sure, but the problem is at the C level, an undefined behavior.
Understanding what the C compiler generates is interesting, but without a corresponding intuition about the optimizer passes, such understanding is shallow and unlikely to be generalized to other problems in the future. You probably won’t even remember this the next time you debug another undefined behavior. On the other hand, if you were to know the optimizer passes employed by the compiler and could deduce this code from that, then it is a useful exercise to enhance your intuition about them.
[-]- exmadscientist 52 minutes agoI think it depends on your experience. I have a lot of experience from the Old Days™ and from developing for microcontrollers, so I find reading assembly very natural and straightforward. When coding for the really small MCUs I've often had the disassembly generated and shown on another window every time I incrementally build, and can check and make sure it's what I was expecting to see.
I do agree that knowledge of compiler optimizations is really important to working this way, though you'll eventually pick them up anyway. I don't see much value in looking at -O0 or -Og disassembly. You want the strongest stuff the compiler can generate if you're going to do this, which is usually either -O3 or -Oz (both of which are strong in their own ways). -O0 disassembly is... just so much pain for so little gain. Besides, -O3 breaks more stuff anyway!
For someone without this level of experience (and who isn't interested in learning)... yeah, I can see why you'd want to do this another way. But if you've got the experience already, it's plenty fast enough.
- niobe 45 minutes agoI think that's the kind of intuitive decision that comes from years of troubleshooting experience. It's not obvious that would be the place to start. It's impressive to me at least he got there.
- Joker_vD 3 hours ago> 1. Explicitly set the C standard to C17 or older, so the code is built using the custom boolean type.
> Option 1) seemed like the easiest one, but it also felt a bit like kicking the can down the road – plus, it introduced the question of which standard to use.
Arguably, that's the sanest one: you can't expect the old C code to follow the rules of the new versions of the language. In a better world, each source file would start with something like
and it would automatically kick off the compatibility mode in the newer compilers, but oh well. Even modern languages such as Go miss this obvious solution.#pragma lang_ver stdc89On the topic of the article, yeah, sticking anything other than 0 or 1 into C99 bool type is UB. Use ints.
[-]- wk_end 1 hour agoYeah, it’s only kicking the can down the road if you’re the actual maintainer of the software.
If you’re just a packager, it’s your job to get the package to build and work correctly; for your own sanity, you should be making minimal changes to the underlying code to facilitate that. Get it building with the old language version and file a bug report.
- Tuna-Fish 3 hours agoRust does the right thing, with the per-crate
statement.edition = - nomel 1 hour ago> you can't expect the old C code to follow the rules of the new versions of the language
Well, to be pedantic, the entire point of the C standard, and the standard body, is that you should expect it to work, as long as you're working within the standard!
[-]- Joker_vD 3 minutes agoNot really, no. Newer versions of standard can (and do, although rarely, I have give it to C standard committee) introduce incompatibilities with earlier versions of standard. E.g. at one point the standard explicitly allowed to #undef "bool", "true", and "false" (and to redefine them later) but IIRC this has been deprecated and removed.
In any case: blindly switching what is essentially a typedef-ed int into _Bool has no business working as expected, since _Bool is a rather quirky type.
- munchler 2 hours ago> memset (sprtemp,-1, sizeof(sprtemp));
Yikes. I think this article undersells the point somewhat. This line of code undermines the type system by spraying -1's into an array of structs, so the only surprise to me is that it took this long to break.
[-]- Sharlin 1 hour ago`spriteframe_t` is defined as
which is okay to splat with all-ones as long as `boolean` is either a typedef for a fundamental type(*) or an enum – because C enums are just ints in a trenchcoat and have no forbidden bit patterns! The C99 `bool`/`_Bool` is, AFAICS, the first type in C that has fewer values than possible bit patterns.typedef struct { // If false use 0 for any position. // Note: as eight entries are available, // we might as well insert the same name eight times. boolean rotate; // Lump to use for view angles 0-7. short lump[8]; // Flip bit (1 = flip) to use for view angles 0-7. byte flip[8]; } spriteframe_t;So yeah, on C99 and C++ this always had UB and could've broken at any time – though I presume compiler devs were not particularly eager to make it ill-behaved just because. But in pre-C99 it's entirely fine, and `rotate == true || rotate == false` could easily be false without UB.
---
(*) other than `char` for which setting the MSB is… not UB but also not the best idea in general.
- direwolf20 1 hour agoC has no particularly strong type system, and it works on typical platforms with all types up to the introduction of _Bool. And maybe float/double, but I think it gives a NaN.
- djoldman 3 hours ago> For this, I used the Godbolt compiler explorer.
> Ah-ha! The generated instructions were ever so slightly different. This would be great news, if it wasn't for me forgetting about one little detail: I have zero knowledge of x86 assembly.
Lol'd at this. I've been there: "ah hah! I found you. hrm, now what does this mean..."
TFA makes me thankful my work doesn't involve C / C++. Learning it earlier in life was enough.
- theamk 3 hours agoThis brings memories - back when I was a student programming in Turbo Pascal 6, I got the same invalid bool (due to array range overflow) which was both true and false at the same time.
- allreduce 16 minutes ago> After consulting some sources, I started translating the assembly instructions into plain Polish
Ah yes, obfuscation
- lowbloodsugar 2 hours agoThat is a fucking travesty. If there’s one thing we should be able to rely on C for it’s that it works with assembly, and it’s always been the case that 0 is false and any other value is true. That’s a compiler bug as far as I’m concerned. I don’t use C++ because it’s gone in a ludicrous unhelpful direction since 2000 or so, but it’s sad to learn that C of all languages decided to favor pedantry over working code.[-]
- munchler 2 hours agoThe code in question is:
Note that this is explicitly comparing two values, which is very different from checking whether a single value is true. Surely you wouldn't expect -1 == 0 to evaluate to true.if (sprtemp[frame].rotate == false)[-]- robinsonb5 8 minutes ago> Surely you wouldn't expect -1 == 0 to evaluate to true.
I wouldn't, no - but that's exactly what's happening in the test case.
Likewise, I wouldn't expect -1 == 1 to evaluate to true, but here we are.
The strict semantics of the new bool type may very well be "correct", and the reversed-test logic used by the compiler is certainly understandable and defensible - but given the long-established practice with integer types - i.e "if(some_var) {...}" and "if(!some_var) {...}" - that non-zero is "true" and zero is "false", it's a shame that the new type is inconsistent with that.
- direwolf20 1 hour agoYou're not wrong. The processor has a perfectly good zero/notzero check. There was absolutely no reason for the compiler to check if x^1==0.
- inglor_cz 1 hour agoI still remember one of my first teachers of programming softly shaming me for writing a condition like
if (something == true)
I haven't done so ever since (1997), and thus I avoid the contrary (with == false) as well, using ! instead. But I would be a lot less ashamed if I knew that there are such conditions in production software.
I would also never guess that the problem described in the article may occur...