(self-reply) One more thing.
> I could use _Bool, but I’d rather stick to a natural word size and stay away from its weird semantics.
This is even more subjective, but personally I like _Bool's semantics. They mean that if an expression works in an `if` statement:
if (flags & FLAG_ALLOCATED)
then you can extract that same expression into a boolean variable:
_Bool need_free = flags & FLAG_ALLOCATED;
The issue is that `flags & FLAG_ALLOCATED` doesn't equal '0 if unset, 1 if set', but '0 if unset, some arbitrary nonzero value if set'. (Specifically it equals FLAG_ALLOCATED if set, which might be 1 by coincidence, but usually isn't.) This kind of punning is fine in an `if` statement, since any nonzero value will make the check pass. And it's fine as written with `_Bool`, since any nonzero integer will be converted to 1 when the expression is implicitly converted to `_Bool`. But if you replace `_Bool` with `int`, then this neither-0-nor-1 value will just stick around in the variable. Which can cause strange consequences. It means that
if (need_free)
will pass, but
if (need_free == true)
will fail. And if you have another pseudo-bool, then
if (need_free == some_other_bool)
might fail even if both variables are considered 'true' (i.e. nonzero), if they happen to have different values.
_Bool solves this problem. Admittedly, the implicitness has downsides. If you're refactoring the code and you decide you don't really need a separate variable, you might try to replace all uses of `need_free` with its definition, not realizing that the implicit conversion to _Bool was doing useful work. So you might end up with incorrect code like:
if ((flags & FLAG_ALLOCATED) == true)
Also, if you are reading a struct from disk or otherwise stuffing it with arbitrary bytes, and the struct has a _Bool, then you risk undefined behavior if the corresponding byte becomes something other than 0 or 1 – because the compiler assumes that the implicit conversion to 0 or 1 has been done already.