How bool is stored in memory

Pages: 1234
Okay, then I understand you reasoning, but I don't think you can jump to the conclusion that that true has to be stored as 1 and false has to be stored as 0 just based on that wording.
I said most likely is stored as. Tested on several compilers and they always converted the value to 0 or 1.

Either way, it would make no sense to store false as anything but 0. You can't even have negatives if its unsigned.

That only leaves the question of whether or not the compiler would allow a value over 1.
Isn't that technically UB?


Probably - but I said for VS...


As usage of a bool in an expression should evaluate to either 1 or 0, probably it is stored as 1 (00000001) or 0 (00000000) - so conversion on the storage but not on the retrieval. For VS, this is born out by the output - but again probably UB...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <iomanip>

int main() {
	bool b {};

	std::cout << "bytes in bool are "  << sizeof(b) << '\n';

	*((uint8_t*)(&b)) = 3;

	std::cout << "b is " << b << std::boolalpha << " as bool " << b << std::noboolalpha << '\n';

	int a { 2 };

	a += b;

	std::cout << a << '\n';
}



bytes in bool are 1
b is 3 as bool true
5


which has the value of b retuning 3 when added to a - even though b is a bool!

Oh the dark corners of C++ UB...

PS Isn't there somewhere in the standard that says a type bool when 'converted' to another integral type gives 1 or 0?

Bottom line - don't try 'clever tricks' with a bool type and accept that it will return either a 1 or a 0.

Also consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <iomanip>

int main() {
	bool b {};

	std::cout << "bytes in bool are "  << sizeof(b) << '\n';

	*((uint8_t*)(&b)) = 3;

	std::cout << "b is " << b << std::boolalpha << " as bool " << b << std::noboolalpha << '\n';

	int a { b };

	std::cout << a << '\n';

	b = 9;

	int c { b };

	std::cout << c << '\n';
}



bytes in bool are 1
b is 3 as bool true
3
1


which also suggests that for VS bool 9 is stored as 00000001 and not 000000101
seeplus wrote:
Isn't there somewhere in the standard that says a type bool when 'converted' to another integral type gives 1 or 0?

It says true is converted to 1 and false is converted to 0.

A prvalue of type bool can be converted to a prvalue of type int, with false becoming zero and true becoming one.
https://eel.is/c++draft/conv.prom#6

Question is, what is std::bit_cast<bool>(static_cast<std::uint8_t>(3))?

True?
False?
Something else?

https://godbolt.org/z/3erGjnYPf
https://godbolt.org/z/693c1Mx3q
For VS 2022 (with no warnings!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <bit>

int main() {
	for (int i = 0; i <= 5; ++i) {
		bool b = std::bit_cast<bool>(static_cast<std::uint8_t>(i));

		std::cout << i << ": b is ";

		if (b == true) {
			std::cout << "true";
		} else if (b == false) {
			std::cout << "false";
		} else {
			std::cout << "something else: " << (int)b;
		}

		std::cout << "\n";
	}
}


displays:


0: b is false
1: b is true
2: b is something else: 2
3: b is something else: 3
4: b is something else: 4
5: b is something else: 5

Last edited on
> can you clarify, i you may please

The standard does specify that bool as 'as an implementation-defined unsigned integer type'; but it does not specify what the precise integer values of true and false should be.

All it says is that true and false should be distinct and how conversions to/from bool would behave.

A prvalue of type bool can be converted to a prvalue of type int, with false becoming zero and true becoming one. https://eel.is/c++draft/conv.prom#6
A prvalue of arithmetic, unscoped enumeration, pointer, or pointer-to-member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true. https://eel.is/c++draft/conv.bool#1


For example, a conforming (but unlikely) implementation may internally store true as 1001ULL and false as either 100823ULL or 0ULL. Ergo, the standard prohibits prefix ++ and -- on operands of type bool.
The operand shall be a modifiable lvalue. The type of the operand shall be an arithmetic type other than cv bool, ... https://eel.is/c++draft/expr.pre.incr#1
Last edited on
It seems that setting a bool converts the value to true/false. You can bypass and set the bits as a regular 1 byte unsigned int (setting it to a negative overflows it).

Question is, what is std::bit_cast<bool>(static_cast<std::uint8_t>(3))?

This is also a hacky way to achieve a bool having a value other than 0 or 1.

bit_cast must not do the same conversion as regularly setting a bool would, then the bool sees it's getting a boolean variable so it doesn't convert to true/false:

int main()
{
bool b{};
*((uint8_t*)(&b)) = 5;
b = std::bit_cast<bool>(static_cast<std::uint8_t>(b));
std::cout << b;
}
5


Which shows a bool does indeed function as a 1 byte unsigned int as expected.


@seeplus

of course, change if (b == true) to if (b) and it'll be true. "true" itself must just evaluate to 1.

I'd assume it's this way on most compilers, but "don't count on it"
Last edited on
So I take it that if a bool stores a value other than true or false (by whatever means), then that is UB when accessing the value of the bool? Clang does one thing and gcc/VS does another.
At the heart of it, it's probably just an if (bool > 1) in assembly determining how to handle the boolean value.
C++17 had this as a footnote:
Using a bool value in ways described by this International Standard as “undefined”, such as by examining the value of an uninitialized automatic object, might cause it to behave as if it is neither true nor false.

Last edited on
zapshe wrote:
Tested on several compilers and they always converted the value to 0 or 1.

Converting true to an integer always results in 1.
Converting false to an integer always results in 0.
Converting 0 to a bool always results in false.
Converting a non-zero integer to a bool always results in true.

Assuming normal conversion, implicit or static_cast.

Knowing these things, which I think is reasonable to expect, then your example could have been written more simply as:

1
2
3
4
5
6
7
8
#include <iostream>
#include <bitset>

int main()
{
	std::cout << std::bitset<8>(1).to_string() << '\n';
	std::cout <<"1\n";
}

What I thought you really intended to demonstrate was how the bool was stored (not how the integer value that the bool was converted to was stored). For that you would have to use something like std::bit_cast (requires C++20) to extract the underlying value.

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <bitset>
#include <bit>
#include <cstdint>

int main()
{
	bool a = 9;
	std::cout << std::bitset<8>(std::bit_cast<std::uint8_t>(a)).to_string() << '\n';
}
https://godbolt.org/z/7TrjjqMPz

zapshe wrote:
Either way, it would make no sense to store false as anything but 0. You can't even have negatives if its unsigned.

Perhaps, but it could make some sense to store true as something other than 1. If the compiler allowed multiple representations of true then I think it could implement conversion from integer to bool very cheaply when the integer type is not bigger than sizeof(bool), because it essentially has to do no work at all, it can just use the integer bit pattern as a bool as-is. This might mean it has to do more work elsewhere, e.g. when converting from bool to int, so it might not be worth it but at least it makes some sense.

zapshe wrote:
That only leaves the question of whether or not the compiler would allow a value over 1.

You could force other values with std::bit_cast and even if it might not be UB (?) it doesn't seem like a good idea.

Peter87 wrote:
Question is, what is std::bit_cast<bool>(static_cast<std::uint8_t>(3))?
zapshe wrote:
This is also a hacky way to achieve a bool having a value other than 0 or 1.

Yes, but not as "hacky" as *((uint8_t*)(&b)) = 3; which I'm sure could be UB.

I'm not sure if my bit_cast hack is also UB, assuming it compiles, but I suspect it might not be given P2624. How else would you get such values that it talks about? https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2624r0.html
Last edited on
What I thought you really intended to demonstrate was how the bool was stored (not how the integer value that the bool was converted to was stored)

There is no "conversion" happening where it matters. There are 8 bits, some are 0s and some are 1s. I really don't understand the argument.

1
2
3
4
5
int main()
{
	bool a = std::bit_cast<bool>(static_cast<std::uint8_t>(5));
	std::cout << std::bitset<8>(a).to_string() << '\n';
}

00000101


but it could make some sense to store true as something other than 1

Yes, I'm merely stating the common case is not this.

Yes, but not as "hacky" as *((uint8_t*)(&b)) = 3;

Seems just as hacky to me.
Last edited on
zapshe wrote:
There is no "conversion" happening where it matters. There are 8 bits, some are 0s and some are 1s. I really don't understand the argument.

Let's imagine that true is stored as 10001000. Then your original example would still print 00000001. If you don't think that is a problem then I apologise.

zapshe wrote:
Seems just as hacky to me.

What I mean by "more hacky" is that it's more likely to cause UB. Using bit_cast is safer.

Note that the standard only explicitly allow pointers to char, unsigned char and std::byte to be used to access the underlying data of any other type. std::uint8_t is often the same type as unsigned char but it doesn't have to be.

This "bit_cast hack" was mostly written in response to what seeplus wrote. I don't think it's relevant to the discussion about your original example.

Last edited on
Let's imagine that true is stored as 10001000

When bool = 1, it's == to true. You can use std::bitset<8>(true).to_string() and you'd get 00000001. "true" is equal to 1.

Why would true be stored as 10001000.. and why would my example output 00000001 if such is the case?
First, false quite clearly 0 and true is "not 0".
We don't need to know what exactly, as there does not seem to be means in the language to see the actual value.

Could you perhaps see the "raw bits" with a debugger, write them into binary file, or something else where the compiler does not do implicit conversions "for your convenience"?
As far as I know, true evaluates to 1. If/while/etc.. likely take non-boolean values and convert them to bool for evaluation (1 or 0).

Could you perhaps see the "raw bits" with a debugger, write them into binary file, or something else where the compiler does not do implicit conversions "for your convenience"?

It has been shown that a bool is practically presented and used the same way as an unsigned integer of 1 byte. Therefore, what could a "conversion" possibly do to the original value?

All this conversion is, is pretending the byte is from an integer rather than a bool - I don't believe it makes any REAL changes.
What does the C++ standard explicitly dictate regarding how Boolean data is stored? AFAIK it doesn't. How the memory is managed is up to each implementation.

Not that the implementations use different memory management for bools. I'd conjecture using a byte (8 bits) makes for easier memory management, alignment, etc.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <bitset>
#include <limits>

int main()
{
    if constexpr( sizeof(bool) == sizeof(unsigned char) )
    {
        using bits = std::bitset< std::numeric_limits<unsigned char>::digits > ;

        bool b = true ;
        unsigned char& c = reinterpret_cast<unsigned char&>(b) ;

        for( unsigned int v : { 0, 1, 99, 128, 255 } )
        {
            c = v ;
            std::cout << v << ' ' << std::boolalpha << b << ' ' << int(b) << ' ' << int(c) << ' ' << bits(c) << '\n' ;
        }
    }
}


On coliru, with g++ and (ancient) clang++:

g++ (GCC) 12.1.0
0 false 0 0 00000000
1 true 1 1 00000001
99 true 99 99 01100011
128 true 128 128 10000000
255 true 255 255 11111111

=================

clang version 5.0.0-3~16.04.1 (tags/RELEASE_500/final)
0 false 0 0 00000000
1 true 1 1 00000001
99 true 1 99 01100011
128 false 0 128 10000000
255 true 1 255 11111111
I think that program has undefined behavior, although I'm not entirely sure.
[expr]/4 is the best justification I could find in the spec:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.

https://eel.is/c++draft/expr#pre-4

It definitely does not violate strict aliasing, but as further evidence of UB, this program produces "neither" with certain (no) compiler flags
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <cstdio>

int main()
{
    bool b = true ;
    unsigned char& c = reinterpret_cast<unsigned char&>(b) ;
    
    c = 99;
    
    switch (b)
    {
      case 0: std::puts("true"); break;
      case 1: std::puts("false"); break;
      default: std::puts("neither"); break;
    }
}

And UbSan complains (with -fsanitize=bool):
/app/example.cpp:10:5: runtime error: load of value 99, which is not a valid value for type 'bool'

See: https://godbolt.org/z/5e9G8jnGz
Last edited on
Pages: 1234