96 Advanced The Hundred
The bool That Was Both
a flag that answers yes to every question
unsigned char packet[] = {0x02}; // one byte, straight off the wire
int main() {
bool encrypted;
std::memcpy(&encrypted, packet, 1);
if (encrypted)
std::cout << "packet is encrypted\n";
if (!encrypted)
std::cout << "packet is not encrypted\n";
std::cout << "encrypted = " << encrypted << "\n";
std::cout << "with boolalpha = " << std::boolalpha << encrypted << "\n";
std::cout << "static_cast<int> = " << static_cast<int>(encrypted) << "\n";
}
Build it and run it. How many of those two messages print?
Answer
Both. Typical output (GCC 11 on x86-64, no optimization):
packet is encrypted
packet is not encrypted
encrypted = 2
with boolalpha = true
static_cast<int> = 2
At -O1 and above the second message disappears — and encrypted still prints as 2.
Why¶
A bool may hold only true or false, and the compiler generates code that counts on
it. GCC tests if (encrypted) with testb %al, %al — nonzero, so true — but negates with
xorl $1, %eax: flipping bit 0 is all "not" needs to mean when the byte is 0 or 1. Yours
is 2, and 2 ^ 1 == 3 is nonzero too, so both branches run. Any other byte pattern in
a bool is undefined behavior, so neither build is wrong: from -O1 on GCC turns the
negation into a compare against zero and only the first message survives, while the raw 2
still sails into operator<<. -Wall -Wextra is silent; -fsanitize=undefined catches it:
The fix¶
A byte from a file, a socket, or an uninitialized struct is not a bool — never memcpy
or reinterpret_cast one into place. Ask a question, and let the compiler build the answer:
Takeaway: undefined behavior is not a wrong value — it is the compiler and the machine disagreeing about what your program says.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo — then again with -O2.