2

I know it is undefined behaviour. Hence the possibility of data race or crash. One can use atomicbool to avoid such possibilities. What I am interested to know about the crash safety.

AFAIU, crash may happen when one thread reads the partially written or torn value by another thread. On the other hand the size of the bool is implementation defined but it hardly make sense to have that greater than the size of pointer in a data model .

is it safe to assume that the memory block of bool will either be updated or not? Therefore, other thread cannot read the torn value hence it is crash safe to read/write global bool from different threads ?

irsis
  • 952
  • 1
  • 13
  • 33
  • I don't quite understand. You're saying that you're deliberately doing something that has undefined behavior, and then asking for guarantees? Or are you asking about the safety of a value that is updated atomically? – paddy Sep 03 '20 at 04:56
  • FYI: [SO: Multithreading program stuck in optimized mode but runs normally in -O0](https://stackoverflow.com/a/58516119/7478597) – Scheff's Cat Sep 03 '20 at 05:01
  • @paddy you may have a situation where having a stale value is acceptable but certainly you can never afford a crash. – irsis Sep 03 '20 at 05:01
  • 1
    I think "crash" safeness should depend on data alignment ```__declspec(align(#)) ``` With default alignment it should be safe to read. Assuming you have switched off compiler optimizations – dgrandm Sep 03 '20 at 05:06
  • Note that the presence of Undefined Behaviour does not just mean that interactions with your bool are not well defined. It means that the *entire* program is invalid and the compiler has no obligation to do anything meaningful for *any* parts of your program. – Jesper Juhl Sep 03 '20 at 05:08
  • 1
    Crashes are your friend. When the bug crashes the program, you know you have a bug. But if the bug doesn't crash the program, you better hope it leaves noticeable traces before disaster strikes. – user4581301 Sep 03 '20 at 05:21

2 Answers2

8

No, it is not safe. Undefined behavior is undefined. There is no rule that says that it can't crash unless you can think of a way that it can crash. I can think of ways it can crash.

For example, suppose you have a function that creates a thread that reads from the same bool that the function writes to without synchronization. Since this is UB, the compiler is free to assume that function will never be called. It is not even required to generate any code for it. If that function is called by an if, the compiler can assume the other branch of the if will always be taken.

And don't you even think about saying "no compiler would ever be that smart". Lots of people used to say things like that and then got burned when compilers got smarter. Over the past decade or two, I've seen dozens of cases of "that new compiler broke my code" when, of course, the code was broken all along.

There have been compilers that have changed code like if (x > 2) y = 3; else y = 4; into code like y = 3; if (x <= 2) y = 4;. That can even be an optimization in some cases.

Don't intentionally make broken code.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • 1
    *Lots of people used to say things like that and then got burned when compilers got smarter.* -- I also know this first hand. Had a job recently where even the lead developer believed that using simple `bool` types were safe, and utterly did not believe compilers were smart enough to reorder the code. – PaulMcKenzie Sep 03 '20 at 05:20
  • @PaulMcKenzie At one time, he was right. But compilers keep getting smarter and what is or isn't an optimization changes as CPU architectures change. It's very easy to get burned. – David Schwartz Sep 03 '20 at 05:21
  • @DavidSchwartz I get your point. Can we call it that crash could be due to logic bug but not the partial write/read ? – irsis Sep 04 '20 at 01:55
  • 1
    @irsis I wouldn't call the intentional choice to do something the compiler is permitted to assume you will never do a "logic bug". The compiler is *supposed* to optimize your code and the compiler is performing entirely valid optimizations here. The "partial write/read" is not a C++ thing. The mistake is assuming that the compiler won't rely on assumptions its explicitly allowed to make. Many common optimizations work this way and expecting them not to happen is just bad reasoning. – David Schwartz Sep 04 '20 at 23:34
3

It is pretty unlikely that you will trigger a crash, but you might trigger other unexpected results:

bool a_global_boolean = false;

void thread_1() {
  sleep(10);
  a_global_boolean = true;
}

void thread_2() {
  while (a_global_boolean == false) {
  }
}

You probably expect that thread_1 and thread_2 will return after 10 seconds. But for thread_2, the compiler is allowed to assume that a_global_boolean will never return, so when optimizations are enabled, that thread may never end.

https://repl.it/repls/UsefulLowAbstracttype#main.cpp (make sure you compile with -O3)

Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • I just had this exact problem, and with a boolean too. Easily fixed by replacing `bool` with `std::atomic` I considered `volatile`, because in my case the bug was caused purely by compiler optimization as described above, but it seems atomic is better because it also guards against undefined behavior caused by race conditions. – theRPGmaster May 24 '22 at 15:50