1

I am a tiny bit confused regarding the following code, as it behaves differently using g++ -O3 and g++ -O1.

#include <iostream>
#include <set>
#include <fstream>
#include <string>
#include <map>
#include <sstream>

int main(int argc, char** argv){
    if(argc<=1){
        std::cerr << "No Input File... Abort" << std::endl;
        return 1;
    }
    std::ifstream in_file;
    in_file.open(argv[1]);
    if(!in_file) {
        std::cerr << "Input \"" << argv[1] << "\" Could not be opened!... Abort" << std::endl;
        return 1;
    }

    std::map<
        std::pair<int,int> /*position,level*/ ,
        int 
    > triangle;

    int level=0; //Counter of current depth in the triangle
    while(!in_file.eof()){
        std::string line;
        std::getline(in_file, line); //Read in complete line (level of triangle)
        std::cout << line << std::endl; //Print what he read
        std::istringstream iss (line); //Split line into pieces
        for(int position=-level;position<=level;position+=2){ //Move through one level of the triangle
            int value;
            iss >> value;
            std::pair<int,int> current_position(position,level); //Position in triangle
            triangle.emplace(current_position, value); //Erzeugung des Punktes und Speicherung des Pointers in der Map
        }
        level++;
    }
    // Print out map contents
    for(int i=0;i<level;++i){
        for(int position=-i;position<=i;position+=2){
            std::pair<int,int> current_position(position,i);
            std::cout << triangle.at(current_position) << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}

This minimal example shall only read in a text-file of e.g. the following type with an empty line at the end:

1
2 3

I understand that if the file has an empty line at the end the stringstream in the loop will be empty and thus stream garbage. However, I do not understand why the behavior is different if I use -O3 or -O1:

g++ test.cpp -Wall -pthread -pedantic -Wextra -O1 -std=c++11 -o test
./test test_file 
1
2 3

1 
2 3 
3 3 3 
g++ test.cpp -Wall -pthread -pedantic -Wextra -O3 -std=c++11 -o test
./test test_file 
1
2 3

1 
2 3 
0 0 0 

This was tested on my system with: gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

As you can see, it seems like the -O3 compiled version leads to the stream forgetting its last input whereas the -O1 compiled version seems to store the value 3 which was last read in although it should be destroyed in the next loop iteration.

  • possibly related: https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-considered-wrong – 463035818_is_not_an_ai Nov 06 '18 at 14:58
  • 1
    If some code behaves differently depending on optimization levels, then it's very likely you have some [*undefined behavior*](https://en.wikipedia.org/wiki/Undefined_behavior) in your code. – Some programmer dude Nov 06 '18 at 14:58
  • "_... thus stream garbage._" - so you **know** you have undefined behaviour, and you're still asking why the behaviour is different? It's because behaviour is left undefined or unspecified partly to allow the optimizer latitude to change the code. – Useless Nov 06 '18 at 15:00
  • As a possible solution, add a check for the empty line. Right now you still process it even if `iss >> value` won't do anything useful. – Some programmer dude Nov 06 '18 at 15:05
  • 1
    Another related post: [Why does stringstream >> change value of target on failure?](https://stackoverflow.com/q/13378989/440558) It seems the compiler/library does different things for this depending on optimization. – Some programmer dude Nov 06 '18 at 15:06
  • Thank you so much for all of these comments Especially the two related questions were really interesting. – someOtherPhysicist Nov 06 '18 at 15:10

1 Answers1

4

Whenever your code behavior is different with different level of optimization, it is a strongest hint that the code exhibits undefined behavior (the other option is a bug in optimizer, which do exist, but it's much less likely).

The reason is the lower optimization level, the more compiler is likely to mechanically translate the C++ code into sequence of ASM commands - exactly as written. However, as optimization levels are clocked up, compilers are becoming inventive, and start making assumptions of the code - the way to think about it is that compilers 'believes' that code never exhibits undefined behavior, and thus omitting any code which would only be executed if undefined behavior was present.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • Thank you very much for this answer. I was really struggling to understand why the -O3 option leads to a (at least to me) more predictable result – someOtherPhysicist Nov 06 '18 at 15:11
  • 2
    Actually the second most common cause is **unspecified behavior**. For instance, there are quite a few cases in which the order of execution is unspecified. (although less now in C++17). E.g. in the expression `f(g(1), g(2), g(3))` it's unspecified which call happens first. Optimizers are known to shuffle this order. There's no guarantee evaluation is left-to-right or right-to-left, an optimizer may decide to start with `g(2)`, – MSalters Nov 06 '18 at 15:31
  • @MSalters true. Especially when inlining is involved. – SergeyA Nov 06 '18 at 15:37