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

164 Views Asked by At

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

2

There are 2 best solutions below

3
Artyer On BEST ANSWER

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.

1
Tony Delroy On

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).