7

Consider the two following programs:

program one

int main()
{
   printf( "hello\n" );
}

program two

int main()
{
   srand( 0 );
   if( rand() ) {
      printf( "hello\n" );
   } else {
      printf( "hello\n" );
   }
}

Do they have the same observable behavior or not? According to C++ Standard (1.9/6) the observable behavior includes:

  • reads and writes to volatile data
  • library I/O functions

Now srand() and rand() are likely not I/O functions (although I have no idea whether a given implementation uses some hardware noise source), but they modify program internal state. Do they manipulate volatile data? I don't know. Calls to printf() are clearly I/O operations and sequences thereof are identical in both programs.

Do the two programs above have the same observable behavior? How do I know if two given programs have the same observable behavior?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • 1
    Is it possible the compiler would optimize out the entire if statement? – i_am_jorf Aug 18 '11 at 05:55
  • 1
    @jeffamaphone: I don't know - usually they don't. – sharptooth Aug 18 '11 at 05:57
  • 2
    GCC (4.6) with `-O3` calls `srand` and `rand`, but does not output the `if` - `puts` gets called unconditionally. – Mat Aug 18 '11 at 06:07
  • @Mat, @sharptooth: so does Clang with `-O2`. The optimization that replaces `printf` by `puts` whenever the character chain ends up with a `'\n'` always amuses me. – Matthieu M. Aug 18 '11 at 06:17
  • @sharptooth: I am curious why you want to know this. I have a vague idea, but please enlighten me. –  Aug 18 '11 at 06:31
  • @jdv-Jan de Vaan: I'm trying to understand how program state and observable behavior relate to each other and so how much is allowed to be optimized away. – sharptooth Aug 18 '11 at 06:33

1 Answers1

8

Do the two programs above have the same observable behavior?

As you say, that depends on whether srand() and rand() have observable side effects. They shouldn't; there can be no external noise source, since the sequence is required to be repeatable for a given seed, and there is no other reason to perform I/O or access volatile data.

If the compiler is able to determine that they don't (e.g. if they are defined inline in a header, or the linker is smart enough to perform extra optimisations), then it would be able to omit them; otherwise, it must assume that they do, and include them. In many implementations, these functions will be in a precompiled library, and the linker won't be that smart, so you will end up calling the functions; however, a decent compiler should notice that both branches of the if are identical and omit the test.

(UPDATE: as noted in the comments, the call to rand() can also only be omitted if the compiler can determine that no future observable behaviour can depend on its side effects).

How do I know if two given programs have the same observable behavior?

In general, that's a very difficult problem, and there will be some programs where it's impossible to tell (for reasons similar to the Halting Problem). For programs as simple as this, you can simply list the observable operations and compare them; if the behaviour depends on the program's input in a non-trivial way, then that soon gets very difficult to do.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 1
    +1, also note that this program is *very* small and limited, but in the general case, where it is not `main` but any other function calling `rand` even if the compiler knew all the internals of `srand` and `rand`, it cannot assume that `rand` will not be used after this function call completes to generate a value that is part of the observable behavior, so it would not be able to optimize away the calls anyway. – David Rodríguez - dribeas Aug 18 '11 at 07:57
  • 1
    What `rand()` might return is a red herring. The compiler can recognize that both branches of the `if` are identical, and suppress the `if`. In this simple case; in a more complicated case, if later output depends on a later call to `rand()`, the compiler cannot suppress the first, since that would change the results of the second (and thus, observable behavior which depends on the second). – James Kanze Aug 18 '11 at 08:06