Home > OS >  Infinite loop from access of improperly created variables
Infinite loop from access of improperly created variables

Time:11-23

Why does this go into an infinite loop and why does it dump char 0x20?

#include <iostream>

struct Outer {
    Outer(std::string &outerString,
          std::string &superfluousString1,
          std::string &superfluousString2) :
            outerString(outerString),
            inner(*this) {}

    struct Inner {
        Inner(Outer &outer) {
            std::cout << outer.outerString;
        }
    } inner;

    std::string &outerString;
};

int main() {
    std::string
            outerString("outerString"),
            superfluousString1("superfluousString1"),
            superfluousString2("superfluousString2");

    Outer outer(outerString, superfluousString1, superfluousString2);

    return 0;
}

I discover this whist I was playing around with inner classes. C is not main language, and I am coming to this from a long hiatus, so I am asking this to improve my knowledge. I do know that I should not be using the fields of Outer before it has been properly created, so this is a very bad thing to do generally. I feel that the real answer is likely that just because I do not have a compiler error does not mean that something is correct.

An interesting side note is that, if I remove either of the superfluous strings, I get a compiler error or it will simply segfault, and that is kind of what I had expected it to do. I boiled this down from a more complex structure that I was playing with, and the elements involved seem to be the minimum required to evoke this response.

CodePudding user response:

I do know that I should not be using the fields of Outer before it has been properly created

Not necessarily. You cannot use members before they are initialised, but you can use members that are already initialised to initialise other members. The catch is that the members are initialised in order of declaration, not in order they appear in initialiser list. inner is declared before outerString, so it's always initialised first.

I feel that the real answer is likely that just because I do not have a compiler error does not mean that something is correct.

That is correct, you have Undefined Behaviour, because you access a reference (outerString) before it's initialised. You compiler should warn you about that if you have warnings enabled (see it online with warnings).

An interesting side note is that, if I remove either of the superfluous strings, I get a compiler error or it will simply segfault, and that is kind of what I had expected it to do.

Undefined Behaviour is undefined, anything can happen. For me, program from one compiler prints nothing and exits normally, from a different compiler prints a lot of whitespace and then crashes.


You can make it work by reordering your members (see it online):

struct Outer {
    Outer(std::string &outerString, std::string &superfluousString, std::string &innerString): outerString(outerString), inner(*this) {}

    std::string &outerString; //declared before 'inner'
    struct Inner {
        Inner(Outer &outer) {
            std::cout << outer.outerString;
        }
    } inner;
};

CodePudding user response:

Class members are initialized in declaration order within the class definition; not in the order of initializers in the ctor-initializer list.

Modern compilers can warn about this discrepancy, you might need to enable more warnings if it didn't happen by default.

So your program does use the reference Outer::outerString before it was bound, causing undefined behaviour.

If you move the declaration of outerString to be before inner within the class definition of Outer, the code should be well-defined .

  • Related