简体   繁体   中英

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM