qpIlIpp
qpIlIpp

Reputation: 29

magic with c++, va_arg doesn't work on 64 bit architecture with double properly

I've a very interesting question, nobody of my friends and colleagues can't help me. So, let's have a look to the next c++ code:

#include <stdio.h>
#include <stdarg.h>
typedef void* VirtualMethodTable;
void funcM(void* __this, ...);
__interface Ix
{
    void __cdecl qq(long a, long b, double x, long c);
};
struct tagInterface
{
    tagInterface()
    {
        VirtualMethodTable* VMT = new VirtualMethodTable[1];
        VMT[0] = (void*)&funcM; //here's funcM assignment
        this->VMT = VMT;
    }
    ~tagInterface(){ delete[] VMT; }
    VirtualMethodTable* VMT;
};
void func1(long a, long b, double x, long c)
{
    //some_logic
}
void funcM(void* __this, ...)
{
    va_list marker;
    va_start(marker, __this);
    marker -= sizeof(__this); // line 1
    tagInterface* inst = va_arg(marker, tagInterface*); //line 2
    //we can comment line 1 and line 2 and it still will work as earlier (doesn't work)
    long l1 = va_arg(marker, long);
    long l2 = va_arg(marker, long);
    double d = va_arg(marker, double);//d = 4.343564450161e-311#DEN, not 3.3
    long l4 = va_arg(marker, long);
    func1(l1, l2, d, l4);
    va_end(marker);
}
long main()
{   
    tagInterface x;
    Ix* ins = (Ix*)((void*)&x);
    long p1 = 1;
    long p2 = 2;
    double p3 = 3.3;
    long p4 = 4;
    ins->qq(p1, p2, p3, p4); //it will call funcM 
}

It works fine on the Win32 architecture (visual studio 2013, win7x64)
But when I start it on x64 the "d" variable in the function funcM have "4.343564450161e-311#DEN" value
Other variables like "l1","l2","l4", "inst" initiate normally.
Although, I've tried to work with 'float', it doesn't work too!
I've searched through all va_arg stack overflow questions and didn't find an answer!
So where have I wrong?
Thank you!
UPDATE 1.:
yeah, its not work because of "conceptual stack doesn't match the physical stack"
The "d" variable goes through xmm3 register and va_arg trying to work with xmm0.
Hope smbd sometime will find it's useful!
UPDATE 2. Solution of problem!
In case of 64-bit programs, the calling convention is different: the values are not always passed via stack: https://msdn.microsoft.com/en-us/library/zthk2dkh.aspx. Some of them are passed via registers. Therefore, va_list (which works with stack and is suitable for variable-argument functions) cannot be used here.
Try this workaround:

struct ClassIx
{
       virtual void __cdecl qq(...);
};
ClassIx* ins = (ClassIx*)((void*)&x);
ins->qq(p1, p2, p3, p4); //it will call funcM


by Viorel_ from MSDN:
https://social.msdn.microsoft.com/Forums/en-US/7a2d9bb5-2b83-4bc5-a018-d7b460fa5550/magic-with-c-vaarg-doesnt-work-on-64-bit-architecture-with-double-properly?forum=vcgeneral

Upvotes: 0

Views: 467

Answers (1)

MSalters
MSalters

Reputation: 179819

va_arg functions must be declared and called as such. You don't call a va_arg function, you call void __cdecl Ix::qq(long a, long b, double x, long c);.

Part of the problem muight be "I just push parametres on the stack". The stack doesn't exist. There's a conceptual C++ call stack (the list of all functions that have been called, but did not yet return, in chronological order, with their arguments) and a physical x64 stack (RBP/RSP registers).

Your problem is that the conceptual stack doesn't match the physical stack. Not all function parameters are on the physical stack. The var_arg mechanism has to dynamically figure out where the function parameters are, though, and that may mean that parameters to variadic functions are on the physical stack.

Upvotes: 1

Related Questions