问题引子

众所周知,在 X64 下,函数参数都是尽量通过寄存器来传递的(浮点数使用专用的浮点数寄存器 xmm, 其他参数使用通用寄存器),只有 abi 里规定的这些寄存器用完之后,才使用栈来传递参数。

stackoverflow 上有这个问题,标准里说makecontext的可变参数都必须是int类型,然后发现 makecontext 的源码里有如下一段注释说明。

 /* Handle arguments.The standard says the parameters must all be int values.  This isan historic accident and would be done differently today.  Forx86-64 all integer values are passed as 64-bit values andtherefore extending the API to copy 64-bit values instead of32-bit ints makes sense.  It does not break existingfunctionality and it does not violate the standard which saysthat passing non-int values means undefined behavior.  */

注释说明里,提到可变参数的整形都是当作 64bit 来处理的,就来深入探究一下x64可变参数的实现原理,包括可变参数当中含有浮点数的情况。

实验代码

实验的代码如下

#include <stdio.h>
#include <stdarg.h>
int f(int x, float y, short a, double b, ...)
{va_list ap;va_start(ap, b);char cc = va_arg(ap, int);float dd = va_arg(ap, double);int ee = va_arg(ap, int);double ff = va_arg(ap, double);int last = va_arg(ap, int);printf("%d  %f  %d  %lf  %d\n", (int)cc, dd, ee, ff, last);for(int i = 0; i < last; ++i){int tmp = va_arg(ap, int);printf("%d\n", tmp);}va_end(ap);
}int main()
{f(1, 2.2f, 3, 4.4,             5, 6.6f, 7, 8.8, 3, 10, 11, 12);return 0;
}

va_list 结构

typedef struct {
unsigned int gp_offset;
unsigned int fp_offset;
void *overflow_arg_area;
void *reg_save_area;
} va_list[1];

g 表示通用寄存器,f 表示浮点数寄存器。

reg_save_area指向栈上的一个特殊位置,这个位置位于栈中间。这里提供6 + 8个位置(前 6 个位置用来保存通用寄存器传递的参数,每个 8 个字节;后8个位置用来保存xmm寄存器传递的浮点数参数,每个16字节)。所有通过寄存器传递的可变参数最后都会复制到这里。比如第5,6两个参数(可变参数)通过通用寄存器传递。那么将占用第5,6两个位置,前4个位置将不会被使用。如果没有可变参数是通过寄存器传递(即可变参数之前已经有不少于 6 个确定的参数),那么这 6 个位置依然存在(占用空间),只是不被使用。

gp_offset表示第一个前边提到的6个位置中保存的可变参数相对于overflow_arg_area的偏移量。如果从第3个数开始是可变参数,那么偏移就是2 * 8 == 16,即下图当中的 0x10.

overflow_arg_area 表示通过栈传递的参数的起始地址。确定的参数和可变参数一起,前边 6 个通过寄存器传递。后边的都是通过栈传递。最后栈传递的参数最先入栈,第 7 个参数最后入栈,拥有最低的地址。所以overflow_arg_area指向第 7 个参数。

x64 下,浮点数通过 xmm寄存器来传递。xmm浮点数寄存器传递的可变参数最终也会复制到栈上。fp_offset表示复制到栈上的可变浮点数参数的起始地址偏离reg_save_area的大小,上边因为中间刚好隔着 6 个8字节的通用寄存器传递的参数。所以如果从第 3 个浮点数(不算非浮点数)开始,那么就是 8 * 6 + 2 * 16 = 80,即下图当中的0x50.

原理粗解

所以原理就是通过 reg_save_area + gp_offset 访问通过通用寄存器传递的可变参数,这些可变参数会复制到栈中间。每访问一个,gp_offset = gp_offset + 8.通过比较 gp_offset 是否大于 0x2f47,来判断是否耗尽这块区域。因为这个区域最大偏移是8 * 5 = 40,其实用不小于 40的数来进行 above判断比较皆可,源代码里使用47,和具体的实现相关。

通过reg_save_area + fp_offset访问浮点数寄存器传递的可变参数,这些可变参数也会复制到栈中间。每访问一个,fp_offset = fp_offset + 16.通过比较fp_offset是否大于0xaf175 来判断是否耗尽这块区域。因为这个区域最大偏移是8 * 6 + 16 * 7 = 160,其实用不小于 160 的数来进行above判断比较皆可,和具体的实现相关。

剩下的参数,是本函数的上级调用者传入调用者的栈,通过栈来传递的,通过overflow_arg_area来访问

栈上内存布局

汇编代码(太长不看)

对应的汇编代码和注释如下

gef➤  disassemble /m main
Dump of assembler code for function main:
26      {
=> 0x0000000000400854 <+0>:     push   %rbp0x0000000000400855 <+1>:     mov    %rsp,%rbp
​
27          f(1, 2.2f, 3, 4.4,             5, 6.6f, 7, 8.8, 3, 10, 11, 12);0x0000000000400858 <+4>:     movsd  0x118(%rip),%xmm2        # 0x4009780x0000000000400860 <+12>:    movsd  0x118(%rip),%xmm1        # 0x4009800x0000000000400868 <+20>:    movsd  0x118(%rip),%xmm0        # 0x4009880x0000000000400870 <+28>:    pushq  $0xc0x0000000000400872 <+30>:    pushq  $0xb0x0000000000400874 <+32>:    mov    $0xa,%r9d  #通用寄存器传递第 6 个非浮点参数0x000000000040087a <+38>:    mov    $0x3,%r8d  #通用寄存器传递第 5 个非浮点参数0x0000000000400880 <+44>:    movapd %xmm2,%xmm30x0000000000400884 <+48>:    mov    $0x7,%ecx   #通用寄存器传递第 4 个非浮点参数0x0000000000400889 <+53>:    movapd %xmm1,%xmm20x000000000040088d <+57>:    mov    $0x5,%edx   #通用寄存器传递第 3 个非浮点参数0x0000000000400892 <+62>:    movapd %xmm0,%xmm1    #xmm 寄存器用来传递浮点数参数。在call 指令前,按顺序从 xmm0 开始,传递从左到右的浮点数参数0x0000000000400896 <+66>:    mov    $0x3,%esi  #通用寄存器传递第 2 个非浮点参数0x000000000040089b <+71>:    movss  0xed(%rip),%xmm0        # 0x4009900x00000000004008a3 <+79>:    mov    $0x1,%edi  #通用寄存器传递第 1 个非浮点参数0x00000000004008a8 <+84>:    mov    $0x4,%eax   #eax 传递浮点数参数的个数。在调用者当中更具 eax 当中的值来进行判断是否需要将浮点数寄存器复制到栈当中。0x00000000004008ad <+89>:    callq  0x400596 <f>  #当然如果没有浮点数参数,直接 mov $0,%eax 这样就不需要进行复制0x00000000004008b2 <+94>:    add    $0x10,%rsp
​
28          return 0;0x00000000004008b6 <+98>:    mov    $0x0,%eax
​
29      }0x00000000004008bb <+103>:   leaveq0x00000000004008bc <+104>:   retq
​
​
​
​
gef➤  disassemble /m f
Dump of assembler code for function f:
4       {0x0000000000400596 <+0>:     push   %rbp0x0000000000400597 <+1>:     mov    %rsp,%rbp0x000000000040059a <+4>:     sub    $0x110,%rsp0x00000000004005a1 <+11>:    mov    %edi,-0xf4(%rbp)  #通用寄存器传递的前 2 个参数是命名参数,直接复制到栈的顶部0x00000000004005a7 <+17>:    movss  %xmm0,-0xf8(%rbp)0x00000000004005af <+25>:    movsd  %xmm1,-0x108(%rbp) #xmm 传递的前两个浮点数参数也是命名参数,直接复制到栈的顶部0x00000000004005b7 <+33>:    mov    %rdx,-0xa0(%rbp)0x00000000004005be <+40>:    mov    %rcx,-0x98(%rbp)0x00000000004005c5 <+47>:    mov    %r8,-0x90(%rbp)0x00000000004005cc <+54>:    mov    %r9,-0x88(%rbp)   #通用寄存器传递的后 4 个参数是可变参数,可变参数复制到 自身 栈中一块单独的保存通用寄存器传递的参数特定区域0x00000000004005d3 <+61>:    test   %al,%al #调用者负责设置好 eax 的值,表明传递的所有的浮点数参数的个数0x00000000004005d5 <+63>:    je     0x4005ef <f+89>0x00000000004005d7 <+65>:    movaps %xmm2,-0x60(%rbp)0x00000000004005db <+69>:    movaps %xmm3,-0x50(%rbp)  #xmm 传递的剩下的浮点数参数是可变参数,可变参数也复制到 自身 栈上一块单独保存浮点数寄存器的特定区域0x00000000004005df <+73>:    movaps %xmm4,-0x40(%rbp)  0x00000000004005e3 <+77>:    movaps %xmm5,-0x30(%rbp)0x00000000004005e7 <+81>:    movaps %xmm6,-0x20(%rbp)  #0x00000000004005eb <+85>:    movaps %xmm7,-0x10(%rbp)0x00000000004005ef <+89>:    mov    %esi,%eax           #edi 和 esi 传递的都是命名参数。所以直接复制到栈顶。0x00000000004005f1 <+91>:    mov    %ax,-0xfc(%rbp)  #通用寄存器传递的参数和浮点数传递的参数保存在栈上的位置相邻
​
5           va_list ap;
6           va_start(ap, b);0x00000000004005f8 <+98>:    movl   $0x10,-0xe8(%rbp) #va_list.gp_offset 第一个通用寄存器传递的可变参数偏离保存的寄存器区域的偏移量0x0000000000400602 <+108>:   movl   $0x50,-0xe4(%rbp) #va_list.fp_offset 第一个浮点数寄存器传递的可变参数偏离保存的寄存器区域的偏移量0x000000000040060c <+118>:   lea    0x10(%rbp),%rax  #这里是 rbp + 说明是调用者的栈,0x10 刚好跳过保存的调用者的 rbp (当前 rbp 指向旧的 rbp) 和 返回地址0x0000000000400610 <+122>:   mov    %rax,-0xe0(%rbp)  #va_list.overflow_arg_area 指向0x0000000000400617 <+129>:   lea    -0xb0(%rbp),%rax0x000000000040061e <+136>:   mov    %rax,-0xd8(%rbp) #va_list.reg_save_area 指向上一个栈帧上保存的寄存器(保存通用寄存器和浮点数寄存器传递的参数)区
​
7
8           char cc = va_arg(ap, int);0x0000000000400625 <+143>:   mov    -0xe8(%rbp),%eax #eax = va_list.gp_offset0x000000000040062b <+149>:   cmp    $0x2f,%eax  #因为通用寄存器只有 6 个位置 48 个字节,所以这里计算偏移量是否超过47,超过说明剩下的可变参数不在这个本栈帧内的这个特殊区域0x000000000040062e <+152>:   ja     0x400653 <f+189>0x0000000000400630 <+154>:   mov    -0xd8(%rbp),%rax #va_list.reg_save_area0x0000000000400637 <+161>:   mov    -0xe8(%rbp),%edx0x000000000040063d <+167>:   mov    %edx,%edx0x000000000040063f <+169>:   add    %rdx,%rax #eax = va_list.gp_offset + va_list.reg_save_area,刚好指向特定区域保存的寄存器传递的参数0x0000000000400642 <+172>:   mov    -0xe8(%rbp),%edx0x0000000000400648 <+178>:   add    $0x8,%edx0x000000000040064b <+181>:   mov    %edx,-0xe8(%rbp) #va_list.gp_offset = va_list.gp_offset + 0x8,偏移量 + 8 指向下一个保存的寄存器参数0x0000000000400651 <+187>:   jmp    0x400665 <f+207>0x0000000000400653 <+189>:   mov    -0xe0(%rbp),%rax0x000000000040065a <+196>:   lea    0x8(%rax),%rdx0x000000000040065e <+200>:   mov    %rdx,-0xe0(%rbp)0x0000000000400665 <+207>:   mov    (%rax),%eax  #eax 指向保存的寄存器参数,取值0x0000000000400667 <+209>:   mov    %al,-0xb5(%rbp) # 复制给栈上的局部变量 cc
​
9           float dd = va_arg(ap, double);0x000000000040066d <+215>:   mov    -0xe4(%rbp),%eax #eax = va_list.fp_offset0x0000000000400673 <+221>:   cmp    $0xaf,%eax #通用寄存器 6 * 8 浮点数寄存器 8 * 16 一共就是 48 + 128 = 128 个字节,这里大于0xaf(127) 说明剩下的浮点数参数都在上个栈上0x0000000000400678 <+226>:   ja     0x40069d <f+263>0x000000000040067a <+228>:   mov    -0xd8(%rbp),%rax #va_list.reg_save_area0x0000000000400681 <+235>:   mov    -0xe4(%rbp),%edx0x0000000000400687 <+241>:   mov    %edx,%edx0x0000000000400689 <+243>:   add    %rdx,%rax #rax = va_list.reg_save_area + va_list.fp_offset 刚好指向特殊区域当中的保存的浮点数寄存器0x000000000040068c <+246>:   mov    -0xe4(%rbp),%edx0x0000000000400692 <+252>:   add    $0x10,%edx 0x0000000000400695 <+255>:   mov    %edx,-0xe4(%rbp) #偏移 + 16,指向下一个 va_list.fp_offset = va_list.fp_offset + 160x000000000040069b <+261>:   jmp    0x4006af <f+281>0x000000000040069d <+263>:   mov    -0xe0(%rbp),%rax0x00000000004006a4 <+270>:   lea    0x8(%rax),%rdx0x00000000004006a8 <+274>:   mov    %rdx,-0xe0(%rbp)0x00000000004006af <+281>:   movsd  (%rax),%xmm0      #这 3 条指令,先用双精度复制到 xmm0 中转0x00000000004006b3 <+285>:   cvtsd2ss %xmm0,%xmm2    #再将双精度转换成单精度,复制给 xmm2 中转0x00000000004006b7 <+289>:   movss  %xmm2,-0xbc(%rbp)  #最后赋值给局部变量 dd
​
10          int ee = va_arg(ap, int);0x00000000004006bf <+297>:   mov    -0xe8(%rbp),%eax0x00000000004006c5 <+303>:   cmp    $0x2f,%eax0x00000000004006c8 <+306>:   ja     0x4006ed <f+343>0x00000000004006ca <+308>:   mov    -0xd8(%rbp),%rax0x00000000004006d1 <+315>:   mov    -0xe8(%rbp),%edx0x00000000004006d7 <+321>:   mov    %edx,%edx0x00000000004006d9 <+323>:   add    %rdx,%rax0x00000000004006dc <+326>:   mov    -0xe8(%rbp),%edx0x00000000004006e2 <+332>:   add    $0x8,%edx0x00000000004006e5 <+335>:   mov    %edx,-0xe8(%rbp)0x00000000004006eb <+341>:   jmp    0x4006ff <f+361>0x00000000004006ed <+343>:   mov    -0xe0(%rbp),%rax0x00000000004006f4 <+350>:   lea    0x8(%rax),%rdx0x00000000004006f8 <+354>:   mov    %rdx,-0xe0(%rbp)0x00000000004006ff <+361>:   mov    (%rax),%eax0x0000000000400701 <+363>:   mov    %eax,-0xc0(%rbp)
​
11          double ff = va_arg(ap, double);0x0000000000400707 <+369>:   mov    -0xe4(%rbp),%eax0x000000000040070d <+375>:   cmp    $0xaf,%eax0x0000000000400712 <+380>:   ja     0x400737 <f+417>0x0000000000400714 <+382>:   mov    -0xd8(%rbp),%rax0x000000000040071b <+389>:   mov    -0xe4(%rbp),%edx0x0000000000400721 <+395>:   mov    %edx,%edx0x0000000000400723 <+397>:   add    %rdx,%rax0x0000000000400726 <+400>:   mov    -0xe4(%rbp),%edx0x000000000040072c <+406>:   add    $0x10,%edx0x000000000040072f <+409>:   mov    %edx,-0xe4(%rbp)0x0000000000400735 <+415>:   jmp    0x400749 <f+435>0x0000000000400737 <+417>:   mov    -0xe0(%rbp),%rax0x000000000040073e <+424>:   lea    0x8(%rax),%rdx0x0000000000400742 <+428>:   mov    %rdx,-0xe0(%rbp)0x0000000000400749 <+435>:   movsd  (%rax),%xmm00x000000000040074d <+439>:   movsd  %xmm0,-0xc8(%rbp)
​
12          int last = va_arg(ap, int);0x0000000000400755 <+447>:   mov    -0xe8(%rbp),%eax0x000000000040075b <+453>:   cmp    $0x2f,%eax0x000000000040075e <+456>:   ja     0x400783 <f+493>0x0000000000400760 <+458>:   mov    -0xd8(%rbp),%rax0x0000000000400767 <+465>:   mov    -0xe8(%rbp),%edx0x000000000040076d <+471>:   mov    %edx,%edx0x000000000040076f <+473>:   add    %rdx,%rax0x0000000000400772 <+476>:   mov    -0xe8(%rbp),%edx0x0000000000400778 <+482>:   add    $0x8,%edx0x000000000040077b <+485>:   mov    %edx,-0xe8(%rbp)0x0000000000400781 <+491>:   jmp    0x400795 <f+511>0x0000000000400783 <+493>:   mov    -0xe0(%rbp),%rax0x000000000040078a <+500>:   lea    0x8(%rax),%rdx0x000000000040078e <+504>:   mov    %rdx,-0xe0(%rbp)0x0000000000400795 <+511>:   mov    (%rax),%eax0x0000000000400797 <+513>:   mov    %eax,-0xcc(%rbp)
​
13
14          printf("%d  %f  %d  %lf  %d\n", (int)cc, dd, ee, ff, last);0x000000000040079d <+519>:   cvtss2sd -0xbc(%rbp),%xmm00x00000000004007a5 <+527>:   movsbl -0xb5(%rbp),%eax0x00000000004007ac <+534>:   mov    -0xcc(%rbp),%ecx0x00000000004007b2 <+540>:   movsd  -0xc8(%rbp),%xmm10x00000000004007ba <+548>:   mov    -0xc0(%rbp),%edx0x00000000004007c0 <+554>:   mov    %eax,%esi0x00000000004007c2 <+556>:   mov    $0x400958,%edi0x00000000004007c7 <+561>:   mov    $0x2,%eax0x00000000004007cc <+566>:   callq  0x4004a0 <printf@plt>
​
15
16          for(int i = 0; i < last; ++i)0x00000000004007d1 <+571>:   movl   $0x0,-0xb4(%rbp)0x00000000004007db <+581>:   jmp    0x400843 <f+685>0x000000000040083c <+678>:   addl   $0x1,-0xb4(%rbp)0x0000000000400843 <+685>:   mov    -0xb4(%rbp),%eax0x0000000000400849 <+691>:   cmp    -0xcc(%rbp),%eax0x000000000040084f <+697>:   jl     0x4007dd <f+583>
​
17          {
18              int tmp = va_arg(ap, int);0x00000000004007dd <+583>:   mov    -0xe8(%rbp),%eax   #eax = va_list.gp_offset0x00000000004007e3 <+589>:   cmp    $0x2f,%eax     #栈上保存的是否耗尽0x00000000004007e6 <+592>:   ja     0x40080b <f+629>0x00000000004007e8 <+594>:   mov    -0xd8(%rbp),%rax0x00000000004007ef <+601>:   mov    -0xe8(%rbp),%edx0x00000000004007f5 <+607>:   mov    %edx,%edx0x00000000004007f7 <+609>:   add    %rdx,%rax0x00000000004007fa <+612>:   mov    -0xe8(%rbp),%edx0x0000000000400800 <+618>:   add    $0x8,%edx0x0000000000400803 <+621>:   mov    %edx,-0xe8(%rbp)0x0000000000400809 <+627>:   jmp    0x40081d <f+647>0x000000000040080b <+629>:   mov    -0xe0(%rbp),%rax      #如果耗尽跳转到这里  eax = va_list.overflow_arg_area 指向上个栈帧0x0000000000400812 <+636>:   lea    0x8(%rax),%rdx        #0x0000000000400816 <+640>:   mov    %rdx,-0xe0(%rbp) #va_list.overflow_arg_area = va_list.overflow_arg_area + 80x000000000040081d <+647>:   mov    (%rax),%eax   #取值0x000000000040081f <+649>:   mov    %eax,-0xd0(%rbp)
​
19              printf("%d\n", tmp);0x0000000000400825 <+655>:   mov    -0xd0(%rbp),%eax0x000000000040082b <+661>:   mov    %eax,%esi0x000000000040082d <+663>:   mov    $0x40096d,%edi0x0000000000400832 <+668>:   mov    $0x0,%eax0x0000000000400837 <+673>:   callq  0x4004a0 <printf@plt>
​
20          }
21
22          va_end(ap);
23      }0x0000000000400851 <+699>:   nop0x0000000000400852 <+700>:   leaveq0x0000000000400853 <+701>:   retq
​

x64可变参数,通用类型的都是按照 8 字节来保存的,所以不存在需要都是整数的说法。

总结

1、通过va_arg来获取参数值,广义的整形(char, short...)指定的类型长度不能小于 int,浮点型不能是 float,否则有警告,会进行类型提升,而且查生成的汇编代码也有问题。

warning: ‘char’ is promoted to ‘int’ when passed through ‘...’

warning: ‘float’ is promoted to ‘double’ when passed through ‘...’

2、只有通过寄存器传递的可变参数,才会赋值到栈上这块特殊的区域,毕竟具名参数都复制到了栈顶。多余的参数(6个之外的整形参数,8个之外的浮点参数)都是在 call指令之前入栈,也就是本函数作为被调用者,在调用者的栈上,而且这些直接通过栈传递的整形都是占用 8 个字节,浮点型,无论是单精度还是双精度也是占用 8 个字节。

如果是下边这种方式调用,因为前边已经有了 8 个浮点数参数,14.14f 将通过栈传递。

f(1, 2.2f, 3, 4.4,             5, 6.6f, 7, 8.8, 3, 10, 11, 12, 'x', 'y', 9.9, 10.1, 11.11, 12.12, 13.3f, 14.4f);
 对应的部分反汇编如下
 0x00000000004008e4 <+0>:     push   %rbp0x00000000004008e5 <+1>:     mov    %rsp,%rbp0x00000000004008e8 <+4>:     movsd  0x158(%rip),%xmm7        # 0x400a480x00000000004008f0 <+12>:    movsd  0x158(%rip),%xmm6        # 0x400a500x00000000004008f8 <+20>:    movsd  0x158(%rip),%xmm5        # 0x400a580x0000000000400900 <+28>:    movsd  0x158(%rip),%xmm4        # 0x400a600x0000000000400908 <+36>:    movsd  0x158(%rip),%xmm3        # 0x400a680x0000000000400910 <+44>:    movsd  0x158(%rip),%xmm2        # 0x400a700x0000000000400918 <+52>:    movsd  0x158(%rip),%xmm1        # 0x400a780x0000000000400920 <+60>:    movsd  0x158(%rip),%xmm0        # 0x400a800x0000000000400928 <+68>:    lea    -0x8(%rsp),%rsp0x000000000040092d <+73>:    movsd  %xmm0,(%rsp)                         #13.3f 放到栈上0x0000000000400932 <+78>:    movsd  0x14e(%rip),%xmm0        # 0x400a880x000000000040093a <+86>:    lea    -0x8(%rsp),%rsp0x000000000040093f <+91>:    movsd  %xmm0,(%rsp)  # 0x400a88             #14.4f 放到栈上0x0000000000400944 <+96>:    pushq  $0x79 #虽然是 char 直接占用 8 个字节0x0000000000400946 <+98>:    pushq  $0x780x0000000000400948 <+100>:   pushq  $0xc0x000000000040094a <+102>:   pushq  $0xb #直接占用 8 个字节0x000000000040094c <+104>:   mov    $0xa,%r9d0x0000000000400952 <+110>:   mov    $0x3,%r8d0x0000000000400958 <+116>:   mov    $0x7,%ecx0x000000000040095d <+121>:   mov    $0x5,%edx0x0000000000400962 <+126>:   mov    $0x3,%esi0x0000000000400967 <+131>:   movss  0x121(%rip),%xmm0        # 0x400a90 #第一个浮点数参数 2.2f 传给寄存器0x000000000040096f <+139>:   mov    $0x1,%edi0x0000000000400974 <+144>:   mov    $0x8,%eax0x0000000000400979 <+149>:   callq  0x400596 <f>0x000000000040097e <+154>:   add    $0x30,%rsp0x0000000000400982 <+158>:   mov    $0x0,%eax0x0000000000400987 <+163>:   leaveq0x0000000000400988 <+164>:   retq

之前通过 va_arg获取参数时,如果在调用者的栈上,无论是整形还是浮点型,va_list.overflow_arg_area 都是增加 8 个字节也说明了这一点。

lea    0x8(%rax),%rdx
mov    %rdx,-0xe0(%rbp) #va_list.overflow_arg_area = va_list.overflow_arg_area + 8

x64下,通过栈传递的整形和浮点型参数都是占用 8 个字节,毕竟使用的是 pushq 指令。但是通过寄存器传递的具名参数,复制到栈上,因为是先开辟栈空间,然后再 mov 复制进栈,都是能合并的合并来节省内存。

通过寄存器传递的可变参数最终复制到栈上的特定区域,整形都是 8 个字节,浮点型因为是 xmm 寄存器,都是 16 个字节。

回归

最初的问题,makecontext的源码内部

va_arg (ap, greg_t); #grep_t 是宏定义 为 long long

通过 greg_t类型来获取参数,所以makecontextx64 环境下,其可变参数可以是任意的整形char, short, long, long long

x64 可变参数原理完全解析相关推荐

  1. c语言va_start函数,va_start和va_end,以及c语言中的可变参数原理

    FROM:http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html 本文主要介绍va_start和va_end的使用及原理. 在 ...

  2. 揭密X86架构C可变参数函数实现原理

    前两天公司论坛有同事在问C语言可变参数函数中的va_start,va_arg 和 va_end 这些接口怎么实现的?我毫不犹豫翻开箱底,将多年前前(算算有十年了)写的文章「亲密接触C可变参数函数」发给 ...

  3. 解析可变参数函数的实现原理(printf,scanf)

    From: http://hi.baidu.com/huifeng00/blog/item/085e8bd198f46ed3a8ec9a0b.html 学习C的语言的时候,肯定接触到标准输出和标准输入 ...

  4. python 可变参数 关键字参数_Python关键字及可变参数*args,**kw原理解析

    可变参数 顾名思义,函数的可变参数是传入的参数可以变化的,1个,2个到任意个.当然可以将这些 参数封装成一个 list 或者 tuple 传入,但不够 pythonic.使用可变参数可以很好解决该问题 ...

  5. python注解实现原理_Python3注解+可变参数实现

    一.说明 1.1 关于注解 关于注解这个东西,最早是在大学学java的时候经常会看到某些方法上边@override之类的东西,一方面不知道其作用但另一方面似乎去掉也没什么影响,所以一直都不怎么在意. ...

  6. c语言怎样获得函数内参数的值_C语言可变参数函数的实现原理

    在本人的<C语言可变参数函数的实现方法>一文中,介绍了如何建立自己的可变参数函数. 下面继续介绍可变参数函数的实现原理. 在汇编语言程序设计中,详细介绍了子程序的实现思想: (1)子程序只 ...

  7. printf 函数使用 可变参数函数实现原理

    一. Printf 和scanf 函数格式 Printf 和 scanf 家族函数都属于可变参数函数(variadic function).这种函数需要固定数量的强制参数,后面是数量可变的可选参数. ...

  8. 理解可变参数va_list、va_start、va_arg、va_end原理及使用方法

     参考: http://www.360doc.com/content/12/0309/10/4025635_192940551.shtml http://www.cnblogs.com/Annie ...

  9. c 语言 可变参数前要加形参,C/C++中可变参数的用法详细解析

    可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等.可变参数是实现printf(),sprintf()等函数的关键之处, ...

最新文章

  1. Windows Phone 7编程学习点滴一——页面切换、返回键重载和工具栏
  2. 深度优先遍历和广度优先遍历_利用广度优先搜索解LeetCode第515题:在每个树行中找最大值...
  3. BZOJ1706奶牛接力跑
  4. Flash Media Server 4.5下载
  5. xsl判断节点存在_HashMap1.8之节点删除分析
  6. 高并发架构系列:如何解决Redis雪崩、穿透、并发等5大难题
  7. Python_60之迭代器模块
  8. 绝佳的录屏编辑神器,一款走遍天下
  9. python实现50行代码_利用 50行Python 代码构建一个在线文本生成器!
  10. 【UKIEPC2017:F题】Flipping Coins(抛硬币求正面朝上个数的期望----概率dp)
  11. Python代码加密 - 4种方案
  12. 谷歌卫星影像免费下载?来看这些软件
  13. 7、核心芯片说明文档
  14. mysql和mongodb查询性能测试_MongoDB 模糊查询,及性能测试
  15. 《五月集训》第二日——字符串
  16. 直播APP开发成品案例
  17. TMC2208电机驱动简介
  18. matlab求三维图形表面积,基于MATLAB的不规则平面立体表面积计算
  19. vue获取麦克风_微信小程序实现录音时的麦克风动画效果实例
  20. 数通基础-二层交换原理

热门文章

  1. h5--新增标签详细的学习
  2. 转载的--------------------HTTP协议中的短轮询、长轮询、长连接和短连接
  3. 回答阿里社招面试如何准备,顺便谈谈对于Java程序猿学习当中各个阶段的建议 - 左潇龙 - 博客园...
  4. 北航2022计算机软件基础期末C++复(预)习
  5. BeautifulSoup用法详解
  6. 以业务管理信息化系统建设推动施工企业数字化转型
  7. 如何高效编写测试用例?【带模板、思维导图】
  8. CentOS 8 上配置 FTP 服务器详细步骤,附截图
  9. 前端vue接口渲染到模板报错解决方案
  10. iframe页面的内嵌框架