Search code examples
c++linuxgccparameter-passingcalling-convention

gcc object passing by value or by address


We have this simple class:

class MyStr
{
public:
    MyStr() { }
    wchar_t* pStr;
};

and this code that uses it:

MyStr ms1, ms2;
ms1.pStr = L"MyStr!"; 
ms2.pStr = L"MyStr2!";

wchar_t buffer[100];
swprintf(buffer, 100, L"%ls - %ls", ms1, ms2);

The assembly that pushes ms1 and ms2 onto the stack looks like:

FF B5 24 FE FF FF    push DWORD PTR [ebp-0x1dc] 
FF B5 20 FE FF FF    push DWORD PTR [ebp-0x1e0] 

It is actually pushing the the values/content of MyStr (which in this case is just pStr) onto the stack.

If we change MyStr to just add a simple destructor:

class MyStr
{
public:
    MyStr() { }
    ~MyStr() { }
    wchar_t* pStr;
};

now the address of ms1 and ms2 are getting passed, instead of their values/contents.

8D 85 24 FE FF FF    lea eax,[ebp-0x1dc] 
50                   push eax 
8D 85 20 FE FF FF    lea eax,[ebp-0x1e0] 
50                   push eax

Visual Studio on Windows gives the same result both ways (always passes values/contents), but gcc on Linux gives these two different results.

  1. Why?
  2. What can we do to keep the destructor but pass by value instead of by reference/address?

The thing we cannot do is change the swprintf line - there are many thousands of them and we are trying to avoid changing them.


Solution

  • You may resort to MACRO

    // Forward call to your method
    #define swprintf(...) my_swprintf(__VA_ARGS__)
    
    // Your class
    class MyStr
    {
    public:
        MyStr() { }
        ~MyStr() { }
        const wchar_t* pStr;
    };
    
    
    // helper function to "fix" parameters
    template <typename T>
    std::enable_if_t<std::is_arithmetic<T>::value || std::is_pointer<T>::value, T>
    normalize(T t) { return t; } // Default one which does nothing
    
    // Fix for your class to return its member.
    const wchar_t* normalize(const MyStr& t) { return t.pStr; }
    
    // Your function which (re-)forward to real swprintf with fixed parameter
    // Extra parent do avoid macro substitution
    template <typename ... Ts>
    auto my_swprintf(wchar_t* buffer, const wchar_t* format, const Ts&... args)
    {
        return (swprintf)(buffer, format, normalize(args)...);
    }