开发者

Can UB cause several single-threaded app runs to produce different outputs?

开发者 https://www.devze.com 2023-01-12 09:24 出处:网络
Is it possible for code that meets the following conditions to produce different outputs for each run for the same input?

Is it possible for code that meets the following conditions to produce different outputs for each run for the same input?

  • The code is single threaded, though it does link against a thread-safe runtime library.
  • There are no explicit calls to rand() or time() or their friends.
  • There are some heap memory allocations.
  • There might be some (buggy) code which resu开发者_运维知识库lts in undefined behavior.


"Undefined behavior" means that anything can happen. This also includes that different things might happen on each run of the program.

For example, if you use uninitialized memory it might be different from program run to program run what exactly that memory contains.

A simple example:

int main() {
  char s[1024];
  s[1023] = '\0';
  std::cout << s << std::endl;
}

This will usually print a different string each time it is run. It doesn't use any heap allocations and I don't think it's even any undefined behavior, so probably it's not the intended solution to your question.

Another example would be that new can return different addresses on each program run (also no UB here):

int main(void) {
   std::cout << new int << std::endl;
}

So even without undefined behavior there are sources of "randomness", so certainly also with undefined behavior different things can happen each program run.


You are going to get a lot of "anything can happen with undefined behavior" answers, so I will not harp on the topic. I will assume that you have a program that you didn't write yourself, should be deterministic, and you have to debug it, or something like that.

  • modern OSes have address randomization, so an undefined behavior that uses addresses as integers can be non-deterministic

  • memory returned by malloc() is not guaranteed to be zeroed, but the OS usually enforces process confidentiality by zeroing the pages before reusing them. So when you malloc(), or use the stack, you should get either a page that has been zeroed or a page that your process filled in itself earlier, so that shouldn't introduce non-determinism.

That's all I can think of for now.


It looks like the last condition is the answer to this question. Except of this, I can think, for example, about time() call and using its result in some calculations.


You may want to read the following posts on why undefined behaviour can lead to unexpected outcomes.


Just to nitpick the following program meets your requirements.

#include <time.h>
#include <iostream>
int main() 
{
    std::cout << time(NULL);
}


Of course.

Many things still vary between executions, even if you don't call rand or time.

For example:

  • the executable code may be loaded to a different address,
  • the heap allocations may return different addresses,
  • a library you use might call rand or time without you knowing it,
  • not all OS'es zero out all process memory when it is allocated, and then you may read a garbage value from memory,
  • even if newly allocated memory pages are zeroed out (as they often are), there is no guarantee that memory allocations always return a new page. They may return a previously used chunk of memory, which will not be zeroed out. And the memory allocator probably won't be deterministic, so sometimes this will happen, other times it won't.

And of course your last point answers it. If your code contains unknown bugs, you can't assume anything about it. What if one of those bugs is that it calls rand even though you thought it wouldn't?

It's best to not try to be clever around UB. If it is undefined, it is undefined, and you're just digging a hole for yourself if you try to reason that "it's not so bad in this case". Because it might be.


In addition to what other people have already said:

If your program interacts with the user, then that most likely introduces another source of randomness. Users may provoke different actions in different orders or with different timing. Depending on what those actions are, there could be subtle differences happening inside the program (such as memory being allocated in a different order).

When you combine that with various forms of undefined behavior (a great example is using uninitialized memory) then you can end up with symptoms that seem to differ each time the program is run and in seemingly unpredictable ways.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号