> 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.
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.
The author knew there was a problem, knew where the problem was, and knew what the problem was, but even with that and an extensive perusal of the standard he still only thinks he knows what part of the standard was violated, presumably because nothing else fit ("didn't explicitly say anything about invalid _Bool values. After some more searching, I believe I've found my answer"). There's no way anyone without omniscience could have predicted this obscure issue being present somewhere in Doom's 60,000 lines of code.
> 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
#pragma lang_ver stdc89
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.
On the topic of the article, yeah, sticking anything other than 0 or 1 into C99 bool type is UB. Use ints.
I know this is likely to be an unpopular take but: I wish it was normal to ship your compiler in your source repo.
Modern compilers are bloated as hell huge things which makes it a bit impractical, but if it was a normal thing to do then we'd probably have optimized the binary sizes somewhat.
I just really like the idea of including _everything_ you need for the project. Also ensures that weird problems like this dont happen. As an extra benefit, if you included the compiler source and a bootstrapping path instead of just the latest binary, then you could easily include project specific compiler / language extensions with no extra effort.
This 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.
That 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.
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.
There's another way to explain the UB: IIRC, any value when stored to a _Bool is supposed to be converted to 0 or 1. The memset() bypasses this rule, boom.
I've never felt the need to use a separate boolean type in C; zero and nonzero are enough and very natural to use.
Seeing "== false" and variations thereof always triggers the suspicion that its author doesn't fully understand boolean expressions. I have once seen the even worse "(x == false) == true".
`bool` is useful as a communication device; if you just use `int` (or `int32_t` or whatever) then it's not exactly clear that the value can only hold `1` or `0` unless you explicitly say it in the documentation. with `bool`, it's clear from the get-go that it's only ever `true` or `false`.
As someone who has always despised the usage of "foo == true" and "foo == false" in predicates instead of just "foo" or "not foo" (or "!foo"), this pleases me.
That practice misses the point of what it means for a value to be a boolean. (Even in C, if it's actually an int.)
19 comments
[ 5.2 ms ] story [ 44.8 ms ] thread> 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.
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.
> 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.On the topic of the article, yeah, sticking anything other than 0 or 1 into C99 bool type is UB. Use ints.
Modern compilers are bloated as hell huge things which makes it a bit impractical, but if it was a normal thing to do then we'd probably have optimized the binary sizes somewhat.
I just really like the idea of including _everything_ you need for the project. Also ensures that weird problems like this dont happen. As an extra benefit, if you included the compiler source and a bootstrapping path instead of just the latest binary, then you could easily include project specific compiler / language extensions with no extra effort.
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.
Ah yes, obfuscation
Seeing "== false" and variations thereof always triggers the suspicion that its author doesn't fully understand boolean expressions. I have once seen the even worse "(x == false) == true".
That practice misses the point of what it means for a value to be a boolean. (Even in C, if it's actually an int.)