在x64中传递的变量大小是多少? [英] What size is vararguments passed in x64?

查看:112
本文介绍了在x64中传递的变量大小是多少?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

大家好。

我的问题是:以什么大小将char,short,__ int32,__ int64传递给接受变量args的x64函数?

我用google搜索了这个但是只发现调用约定,没有通过args的大小。

通过调试,我发现char,short,__ int32作为32位值传递,__ int64作为64位值传递。但我没有找到正式的参考资料。它是否正确?任何文章?提前致谢

mr.abzadeh



编辑:按大小我的意思是填充。 char,short,int32填充为32位值。每个参数在堆栈中是64位对齐,但较小的参数填充为32位,而不是64位。



编辑:反汇编这个简单的代码显示(__int64 )ch1作为64位值传递(mov qword ptr [rsp + 20h],rax),char,short,__ int32作为32位传递(regesters r9d,r8d,edx)

  char  ch1 = '  A'< /跨度>; 
// 00007FF68CE27265 C6 44 24 30 41 mov byte ptr [ch1],41h
wprintf(L %c%d%ld%lld,ch1,( short )ch1,( __ int32 )ch1,( __ int64 )CH1);
// 00007FF68CE2726A 48 0F BE 44 24 30 movsx rax,byte ptr [ch1]
// 00007FF68CE27270 0F BE 4C 24 30 movsx ecx,byte ptr [ch1]
// 00007FF68CE27275 0F BE 54 24 30 movsx edx,byte ptr [ch1]
< span class =code-comment> //
00007FF68CE2727A 44 0F BE 44 24 30 movsx r8d,byte ptr [ch1]
// 00007FF68CE27280 44 89 44 24 34 mov dword ptr [rsp + 34h],r8d
// 00007FF68CE27285 48 89 44 24 20 mov qword ptr [rsp + 20h],rax
// 00007FF68CE2728A 44 8B C9 mov r9d,ecx
// 00007FF68CE2728D 44 8B C2 mov r8d,edx
// 00007FF68CE27290 8B 44 24 34 mov eax,dword ptr [rsp + 34h]
// 00007FF68CE27294 8B D0 mov edx,eax
// 00007FF68CE27296 48 8D 0D 7B 68 2B 00 lea rcx,[byValue + 298h(07FF68D0DDB18h)]
// 00007FF68CE2729D FF 15 C5 C9 3C 00 call qword ptr [__imp_wprintf(07FF68D1F3C68h)]

解决方案

x64不一定会影响变量的大小:char总是8位(在现代计算机上,无论如何),一个短16,一个int32将是32,和int64会是64.

这并不意味着它们将被传递给该大小的函数,并且当将参数添加到堆栈时参数通常不会合并,而是拉伸到保留堆栈粒度,因为堆栈位置总是在一定数量的字节组中分配 - 通常是4个字节或32位。

因此单个字节值将占用一个整个堆栈位置 - 32位 - 短小和int32。并且int64将占用两个堆栈位置--64位。



这是接受可变数量的参数并不重要,所有功能都会发生这种情况,甚至结构,除非他们打包。如果堆栈条目总是相同的大小,它只是简单得多(并且在时间方面更有效)。



如果你愿意,可以考虑一下成堆的硬币:如果它们都是相同的尺寸,那么与在同一堆叠中开始组合4种截然不同的尺寸硬币相比,它更容易使用! :laugh:


标准和变量的X64 ABI(应用程序二进制接口)的唯一区别是浮点参数在XMM寄存器和标准寄存器中传递,所有其他特性是相同的:

1.前4个参数作为FASTCALL在寄存器中传递。超过4的参数在堆栈上传递。

2.从8到64位的参数在寄存器右对齐中传递。意味着char参数将占用寄存器的低8位,widechar低16位,int低32位,I64占整个寄存器。同一系统用于堆栈上始终具有64位维度的参数,表示每个推送或弹出都是64位宽(当CPU处于x64模式时,这也是硬件要求)。请参阅 MSDN文档 [ ^ ]:

Quote:

前四个整数参数在寄存器中传递。在RCX,RDX,R8和R9中传递整数值(按从左到右的顺序)。在堆栈上传递五个或更多的参数。所有参数在寄存器中都是右对齐的。这样做是为了让被调用者可以在需要时忽略寄存器的高位,并且只能访问必要的寄存器部分。





3。超过64位的其他大小,即__m128,仅通过引用传递。编译器创建一个隐藏的变量本地副本,然后将指针传递给这样的隐藏副本。



所有信息都可以在MS网站上找到其他 [ ^ ]。仔细阅读,你将得到你所需要的一切。



编辑添加解释,因为答案升级。



编译器用于右对齐的方式是编译器特定的,因此它可以是简单的字节移动,符号扩展移动到16,32或64位。在你的情况下,前四个指令取决于你应用的演员表。



请参考代码:

  char  ch1 = '  A' ; 
// 00007FF68CE27265 C6 44 24 30 41 mov byte ptr [ch1],41h
wprintf(L %c%d%ld%lld,ch1,( short )ch1,(__ int32)ch1,(__ int64)ch1);
// 这只是一个转换,因为第五个参数将在堆栈上传递
// 00007FF68CE2726A movsx rax,byte ptr [ch1] //转换为64位int(__ int64)ch1。

// 对值进行转换以传入寄存器RCX RDX R8 R9
// 00007FF68CE27270 movsx ecx,byte ptr [ch1] //转换为32位int (__int32)ch1
// 00007FF68CE27275 movsx edx,byte ptr [ch1] //字节扩展到32位
// 00007FF68CE2727A movsx r8d,byte ptr [ch1] //转换为短扩展到32位(短)ch1

// 此代码已通过优化生成,而这显示在
// 下一行,其中转换值的副本保存在本地变量@ [rsp + 34h]
// 00007FF68CE27280 44 89 44 24 34 mov dword ptr [rsp + 34h],r8d

// 推送堆栈上的第5个参数。附:堆栈宽度为64位。
// 00007FF68CE27285 48 89 44 24 20 mov qword ptr [ rsp + 20h],rax

// 现在加载寄存器RCX RDX根据x64 ABI要求的R8 R9
// RCX =格式字符串
// RDX = ch1
// R8 =(短)ch1
// R9 =(__ int32)ch1
// 00007FF68CE2728A 44 8B C9 mov r9d,ecx
// 00007FF68CE2728D 44 8B C2 mov r8d,edx
// 00007FF68CE27290 8B 44 24 34 mov eax,dword ptr [rsp + 34h] //获取值...
// 00007FF68CE27294 8B D0 mov edx,eax /将其存储在RDX的低32位
// 00007FF68CE27296 48 8D 0D 7B 68 2B 00 lea rcx,[byValue + 298h(07FF68D0DDB18h)] //格式string

// 调用wprintf()
// 00007FF68CE2729D FF 15 C5 C9 3C 00 call qword ptr [__imp_wprintf(07FF68D1F3C68h)]


Hello guys.
My question is: In what size char, short, __int32, __int64 are passed to a x64 function accepting variable args?
I googled this but only found calling convention, nothing about the size of args passed.
By debugging, I found that char, short, __int32 are passed as 32 bit values, __int64 is passed as a 64 bit value. but no formal reference I found for this. Is this correct? any article? Thanks in advance
mr.abzadeh

Edit: by size I meant padding. char, short, int32 are padded to 32 bit values. Every param is 64 bit aligned in stack, but smaller size params are padded to 32 bit, not 64 bit.

Edit: Disassembly of this simple code shows that (__int64)ch1 is passed as 64 bit value(mov qword ptr [rsp+20h],rax), char, short, __int32 are passed as 32 bit(regesters r9d, r8d, edx)

char ch1 = 'A';
//00007FF68CE27265 C6 44 24 30 41       mov         byte ptr [ch1],41h  
	wprintf(L"%c %d %ld %lld", ch1, (short)ch1, (__int32)ch1, (__int64)ch1);
//00007FF68CE2726A 48 0F BE 44 24 30    movsx       rax,byte ptr [ch1]  
//00007FF68CE27270 0F BE 4C 24 30       movsx       ecx,byte ptr [ch1]  
//00007FF68CE27275 0F BE 54 24 30       movsx       edx,byte ptr [ch1]  
//00007FF68CE2727A 44 0F BE 44 24 30    movsx       r8d,byte ptr [ch1]  
//00007FF68CE27280 44 89 44 24 34       mov         dword ptr [rsp+34h],r8d  
//00007FF68CE27285 48 89 44 24 20       mov         qword ptr [rsp+20h],rax  
//00007FF68CE2728A 44 8B C9             mov         r9d,ecx  
//00007FF68CE2728D 44 8B C2             mov         r8d,edx  
//00007FF68CE27290 8B 44 24 34          mov         eax,dword ptr [rsp+34h]  
//00007FF68CE27294 8B D0                mov         edx,eax  
//00007FF68CE27296 48 8D 0D 7B 68 2B 00 lea         rcx,[byValue+298h (07FF68D0DDB18h)]  
//00007FF68CE2729D FF 15 C5 C9 3C 00    call        qword ptr [__imp_wprintf (07FF68D1F3C68h)]  

解决方案

x64 doesn't necessarily affect the size of variables: a char will always be 8 bits (on modern computers, anyway), a short 16, an int32 will be 32, and an int64 will be 64.
That doesn't mean that they will be passed to a function as that size however, and parameters are generally not "combined" when the are added to the stack, but "stretched" to preserve the stack granularity becuase stack locations are always assigned in "groups" of a certain number of bytes - normally 4 bytes or 32 bits.
So a single byte value will occupy one whole stack location - 32 bits - as will a short and a int32. And int64 will occupy two stack locations - 64 bits.

It doesn't matter that this is accepting a variable number of parameters, this happens with all functions, and even with structs unless they are "packed". It's simply a lot simpler (and more efficient in time terms) if the stack entries are always the same size.

If you like, think about it in terms of a stacks of coins: if they are all the same size it's a lot easier to work with than if you start combining 4 wildly different size coins in the same stack! :laugh:


The only difference in X64 ABI (Application binary interface) for standard and varargs is that floating point arguments are passed in XMM registers and standard registers, all other characteristics are the same:
1. The first 4 parameters are passed in registers as FASTCALL. Parameters in excess of 4 are passed on the stack.
2. The parameters from 8 up to 64 bits are passed in registers right justified. Means that a char parameter will occupy the lower 8 bits of the register, a widechar the lower 16 bits, an int the lower 32bits and an I64 the whole register. The same system is used for parameters on the stack that has always a dimension of 64bits, menaing that each push or pop is 64bits wide (this is also an HW requirement when the CPU is in x64 mode). Please see MSDN documentation[^]:

Quote:

The first four integer arguments are passed in registers. Integer values are passed (in order left to right) in RCX, RDX, R8, and R9. Arguments five and higher are passed on the stack. All arguments are right-justified in registers. This is done so the callee can ignore the upper bits of the register if need be and can access only the portion of the register necessary.



3. Other size in excess of 64 bits, i.e. __m128, are passed only by reference. The compiler creates an hidden local copy of the variable then pass the pointer to such hidden copy.

All information are available on the MS site here[^]. Reading carefully you'll get all you need.

EDIT to add explanation due to answer upgrade.

The way used by compiler for the right justify are compiler specific, so it could be a simple byte move, a sign extended move to a 16, 32 or 64bits. In your case the first four instructions depends on the cast you applied.

Please refer to the code:

char ch1 = 'A';
//00007FF68CE27265 C6 44 24 30 41       mov         byte ptr [ch1],41h
    wprintf(L"%c %d %ld %lld", ch1, (short)ch1, (__int32)ch1, (__int64)ch1);
    //This is just a conversion because the 5th parameter will be passed on the stack
//00007FF68CE2726A movsx       rax,byte ptr [ch1]    //Cast to 64 bits int (__int64)ch1.

    //Apply casts for values to pass in registers RCX RDX R8 R9
//00007FF68CE27270 movsx       ecx,byte ptr [ch1]    //Cast to 32 bits int (__int32)ch1
//00007FF68CE27275 movsx       edx,byte ptr [ch1]    //Byte extended to 32bits
//00007FF68CE2727A movsx       r8d,byte ptr [ch1]    //Cast to short extended to 32bits (short)ch1

    //This code have been generated with optimizations off this clearly exposed on
    //the next line where a copy of converted value is saved in local variable @ [rsp+34h]
//00007FF68CE27280 44 89 44 24 34       mov         dword ptr [rsp+34h],r8d

    //Push the 5th parameter on the stack. P.s. The stack is 64bits wide.
//00007FF68CE27285 48 89 44 24 20       mov         qword ptr [rsp+20h],rax

    //Now loads the registers RCX RDX R8 R9 as required by x64 ABI
    //RCX = format string
    //RDX = ch1
    //R8  = (short)ch1
    //R9  = (__int32)ch1
//00007FF68CE2728A 44 8B C9             mov         r9d,ecx
//00007FF68CE2728D 44 8B C2             mov         r8d,edx
//00007FF68CE27290 8B 44 24 34          mov         eax,dword ptr [rsp+34h]  //Get the value and...
//00007FF68CE27294 8B D0                mov         edx,eax  /store it in the low 32 bits of RDX
//00007FF68CE27296 48 8D 0D 7B 68 2B 00 lea         rcx,[byValue+298h (07FF68D0DDB18h)]  //format string

    //Call wprintf()
//00007FF68CE2729D FF 15 C5 C9 3C 00    call        qword ptr [__imp_wprintf (07FF68D1F3C68h)]


这篇关于在x64中传递的变量大小是多少?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆