-1

Could an implicit compiler created default constructor have more than a null body? According to IBM's website, the answer is: no.

I have this project that's kind of stumping me though:

This is where an instance of a class called StackWalkerToConsole with no defined default constructor gets declared:

void func5()
{
  StackWalkerToConsole sw;
  ...
}

This somehow causes one of the user-defined constructors of its parent class to get called:

StackWalker::StackWalker(int options, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess)
{
  // The function has a body, I just removed it for clarity's sake.
  ...
}

So I put a breakpoint on this constructor, and using the callstack I looked at the default constructor for StackWalkerToConsole. Since it's not defined in the source code, I could only look at its dissassembly:

StackWalker_VC2017.exe!StackWalkerToConsole::StackWalkerToConsole(void):
0000000140008210  mov         qword ptr [rsp+8],rcx  
0000000140008215  push        rdi  
0000000140008216  sub         rsp,40h  
000000014000821A  call        qword ptr [__imp_GetCurrentProcess (014012E060h)]  
0000000140008220  mov         qword ptr [rsp+30h],rax  
0000000140008225  call        qword ptr [__imp_GetCurrentProcessId (014012E068h)]  
000000014000822B  mov         rcx,qword ptr [rsp+30h]  
0000000140008230  mov         qword ptr [rsp+20h],rcx  
0000000140008235  mov         r9d,eax  
0000000140008238  xor         r8d,r8d  
000000014000823B  mov         edx,3Fh  
0000000140008240  mov         rcx,qword ptr [this]
  
0000000140008245  call        StackWalker::StackWalker (0140002F1Dh)  

000000014000824A  mov         rax,qword ptr [this]  
000000014000824F  lea         rcx,[StackWalkerToConsole::`vftable' (01400ED6C0h)]  
0000000140008256  mov         qword ptr [rax],rcx  
0000000140008259  mov         rax,qword ptr [this]  
000000014000825E  add         rsp,40h  
0000000140008262  pop         rdi 

This compiler defined constructor is calling 2 WinApi functions: GetCurrentProcess and GetCurrentProcessId and calling the user-defined constructor for StackWalker.

Does anyone know why this is? I should mention, the parent class StackWalker does not have any user defined default constructors either.

If further information is required I'd be glad to supply it. Thank you for reading this far.

Edit: This is the github for the code: It's a tiny project, just 1 cpp file and 1 main.cpp file to test it. I'm assuming no one has the time and energy to go through the code, but putting it in here just in case.

In the post I'm referencing Line 41 from main.cpp and Line 929 from StackWalker.cpp

18
  • 2
    the body is empty, but before the constructor runs the members and base classes are initialized Commented Dec 8, 2023 at 11:37
  • 2
    To instantiate StackWalkerToConsole, its default constructor must be called, because you do not specify any => all bases and all elements must be default constructed. That includes the default constructing of StackWalker, and it seems that this one calls ` GetCurrentProcess()`. Commented Dec 8, 2023 at 11:38
  • 1
    @Horace - If any base classes or members of your class don't have a default constructor, then the compiler cannot automatically generate a default constructor. Non-class members (e.g. of type int) will be default-initialised. Commented Dec 8, 2023 at 11:44
  • 1
    @Horace, look again. If StackWalkerToConsole inherits from StackWalker and you do not define any constructor, StackWalker is default constructible. You can try to create StackWalker test;, it is possible only if StackWalker has a default constructor. Commented Dec 8, 2023 at 11:56
  • 1
    I'm going to vote to close because questions must be self-contained, not relying on external links to be answerable. Please construct a short minimal reproducible example that someone could copy from the question, compile, then run to reproduce your result. Commented Dec 9, 2023 at 11:20

2 Answers 2

7

The class you've referenced:

class StackWalkerToConsole : public StackWalker
{
protected:
  virtual void OnOutput(LPCSTR szText) { printf("%s", szText); }
};

Has an implicitly-declared default constructor that looks similar to this:

StackWalkerToConsole::StackWalkerToConsole() : StackWalker() {}

Which calls this constructor of StackWalker:

  StackWalker(int    options = OptionsAll,
              LPCSTR szSymPath = NULL,
              DWORD  dwProcessId = GetCurrentProcessId(),
              HANDLE hProcess = GetCurrentProcess());

(StackWalker does have a user-defined default constructor, it's this one that can be called with zero arguments)

So while there's no body of StackWalkerToConsole's default constructor, its base class is initialized using the default arguments StackWalker(OptionsAll, NULL, GetCurrentProcessId(), GetCurrentProcess()) and the body of the StackWalker constructor is run.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you so much I feel like such an idiot. I'm a bit new to C++ and didn't imagine the default constructor would be defined in the header file since my brain was like: code go in cpp, declaration go in .h.
@Horace: The StackWalker( int options = OptionsAll, ... actually is just a declaration. The definition is in some .cpp somewhere, called by the call StackWalker::StackWalker (0140002F1Dh) in the implicitly-generated StackWalkerToConsole::StackWalkerToConsole() default constructor in the disassembly in your question. The default values are themselves not simple, so that implicit default constructor which works as if it had an empty body {} in C++ terms still compiles to a significant amount of asm that makes 2 function calls to get the defaulted args for the parent constructor.
@PeterCordes Interesting, you're right it does still count as a declaration in spite of the default values for 2 of the arguments being calls to functions themselves. Thank you for your input
2

Just focusing narrowly on your title question, namely...

Could an implicit compiler created default constructor have more than a null body?

The C++20 Standard says:

An implicitly-defined (9.5.2) default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with no ctor-initializer (11.9.3) and an empty compound-statement.

The IBM website you linked to claims "The constructor will have no constructor initializer and a null body." which I think is an attempt to paraphrase the same requirement. As best I could find, the Standard only refers to "null body" once in [stmt.expr] - saying that an expression-statement without an expression can be useful in e.g. while (condition) ; to supply a null body to the iteration statement. I'm not sure it's a good idea for IBM to use such a poorly, implicitly defined term, which some people might think means the default constructor isn't allowed to do anything. What it does do is perform whatever initialisation the (virtual and non-virtual) bases need, including setting its own vtable pointer - if any - as the base class constructors run, and have all the non-static member constructors run based on any constructor arguments provided inline in the class definition, or failing that - using their default constructors. So, an implicit default constructor can still be doing quite a lot of implicit work.

A "null body" doesn't mean calling the constructor is a no-op (i.e. do-nothing function).

1 Comment

Thank you, I appreciate the extra input there. I was indeed a bit misled by the term null body there. That's very good to know

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.