views:

79

answers:

6

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 results in undefined behavior.
+8  A: 

"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.

sth
Ok, fair enough. I will add "can it actually happen in a real system?"
zr
@zr: yes. Some systems will zero memory you allocate with `new` (for example), but others don't.
Jerry Coffin
I don't know about C++, but the C equivalent of this program invokes undefined behavior: Uninitialized memory is "indeterminate", "indeterminate" is defined as "either an unspecified value or a trap representation", and accessing a trap representation is undefined behavior.
Pascal Cuoq
I realy like the second example. Thanks!
zr
@Pascal: to be really pedantic, it's implementation-dependent whether it's undefined behaviour to access uninitialized memory in general, because if the implementation has no trap values then it can't be a trap value. But on any implementation, `char` cannot have trap values. So sth's first snippet outputs a value which is unspecified, but has no actual UB. I think - I'm assuming your quote is the only relevant statement on the subject.
Steve Jessop
@Steve: that depends on whether you define UB as "undefined according to C++ spec" or "not defined by either the C++ spec or the implementation". If you want to be pedantic, UB is a C++ term, and it refers solely to the C++ spec. It is still UB, whether or not bad things can happen in practice on any system.
jalf
@jalf: no, we can talk about the C++ standard defining behaviour for implementations with certain characteristics. For instance we don't say, "`1 << 16` is UB in C++", we say, "`1 << 16` is defined in C++ if `int` is at least 17 bits (which in turn is implementation-defined), UB if less". Based on the quote we're looking at, using uninitialized values gives unspecified results if there cannot be trap values (which is implementation-defined), UB if there can. But anyway, for `char` there cannot, the standard forbids it.
Steve Jessop
... although come to think of it, maybe the standard does permit trap values for `char` in uninitialized objects, just not in any initialized data of any type. Can't remember. I'm thinking about for example a machine with 9-bit byte emulating 8-bit, so that the additional bit is "invisible to C++" for all purposes except that maybe everything that writes sets that top bit to 0, and anything that reads traps if it is non-zero. Not sure whether that's legal or not...
Steve Jessop
A: 

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.

Alex Farber
A: 

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.

Pascal Cuoq
"that shouldn't introduce non-determinism." - it can, though, since the memory allocator itself need not be deterministic. If you allocate, fill, and free three allocations, then make a new allocation, then you could get back any of the original three (or something different). OK, so it's *unlikely* that there would be non-deterministic behaviour in such a simple example, but there *could* be. For example on a given implementation you might get back the first of the allocations if the OS has asked the process to release memory back to the system at a crucial moment, and the third otherwise.
Steve Jessop
A: 

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);
}
Motti
Good catch. I will add a requirement which prohibits usage of time().
zr
A: 

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.

jalf
A: 

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.

TheUndeadFish