10

Actually the segmentation fault happens in another program I tried to compile which happens because of this behaviour.

My question is:

This is a bug or my fault?

Reproducible in any way (even if the something field is private or protected) and here is my example:

main.cc:

#include <iostream>
class Test {
    public:
        const char* something = "SOMETHING HERE!!!";
        Test(const int& number) : Test(something, number) { }
        // XXX: changed `something` to `_something` to make it different
        Test(const char* _something, const int& number) {
            std::cout << _something << std::endl;
            std::cout << number << std::endl; }
        ~Test() { }
};

int main(int argc, char* argv[]) {
    Test te1(345);
    Test te2("asdasdad", 34523);
    return 0;
}

And here is what happens when compiling with:

g++ main.cc -Os -o main

and running with:

./main

the output is:

pi@pi:~/ $ ./main
A"�~ <-- this is random
345
asdasdad
34523

But when I enable optimization with -O0 or -O1 or -O2 ... the output is only a new line:

pi@pi:~/ $ ./main
pi@pi:~/ $

G++ version:

pi@pi:~/ $ g++ --version
g++ (Raspbian 6.3.0-18+rpi1) 6.3.0 20170516
Quentin
  • 62,093
  • 7
  • 131
  • 191
Memos Electron
  • 660
  • 5
  • 26

2 Answers2

20
const char* something = "SOMETHING HERE!!!";

The default-initializer on the right is, as its name implies, only used when you don't provide an explicit initializer in a constructor's initializer list. Let's look at yours:

Test(const int& number) : Test(something, number) { }

Okay, we're delegating to another constructor. That other constructor will perform full initialization, so the default initializer is not used. But... we're passing in the uninitialized value of something as parameter.

Test(const char* _something, const int& number) { /* ... */ }

Uh-oh. Now we're trying to use the value of _something, which is a copy of something, which is indeterminate. Undefined Behaviour and fire ensue.

You really shouldn't pass the value of a class member as parameter to its constructor, unless you have an infinite supply of fireproof chickens and eggs.


The behaviour you're looking for can be obtained by putting the default value in the call to the delegate constructor:

Test(const int& number) : Test("SOMETHING HERE!!!", number) { }

... or keeping it in a dedicated static variable:

static constexpr char *const defaultSomething = "SOMETHING HERE!!!";
Test(const int& number) : Test(defaultSomething, number) { }
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • 1
    Alternatively, a private default constructor would let you write, `Test(const char* _something, const int& number) : Test() { /* ... */ }` – Davislor Dec 24 '17 at 17:49
  • 1
    "You really shouldn't pass the value of a class member as parameter to its constructor, unless you have an infinite supply of fireproof chickens and eggs." -- well done – sehe Dec 25 '17 at 16:38
14

This is a bug or my fault?

Oh, it's your fault. The default member initializer is only used to initialize the member object in a non-delegating constructor. According to [class.base.init]/9, emphasis mine:

In a non-delegating constructor, if a given potentially constructed subobject is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer), then

  • if the entity is a non-static data member that has a default member initializer and either [...] the entity is initialized from its default member initializer as specified in [dcl.init];

So something isn't initialized when you pass it to the target constructor. Your program has undefined behavior, and goes bust.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458