stdarg.h
这里用到的是stdarg.h
这个库, 可以在C语言里面实现可变长参数.
当然C++会简单得多, C++11之后的模板原生支持可变长参数.
几个函数va_list、va_start、va_arg、va_end,定义在stdarg.h
内存结构
先需要理解C/C++函数入参的顺序.
按照以下的demo, 将其翻译成汇编代码.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std ;
int sum ( const int & a , const int & b , const int & c )
{
int d = 0 ;
d = a + b + c ;
return d ;
}
int main ()
{
int s = sum ( 1 , 2 , 3 );
cout << s << endl ;
return 1 ;
}
首先是main函数, 主体部分的汇编
1
2
3
4
5
6
7
8
9
mov DWORD PTR [ rbp - 0x10 ], 0x3
mov DWORD PTR [ rbp - 0xc ], 0x2
mov DWORD PTR [ rbp - 0x8 ], 0x1
lea rdx ,[ rbp - 0x10 ]
lea rcx ,[ rbp - 0xc ]
lea rax ,[ rbp - 0x8 ]
mov rsi , rcx
mov rdi , rax
call 401172 < sum ( int const & , int const & , int const & ) >
可以看到, main函数调用了sum函数, 首先搜获取三个参数, 1, 2, 3; 获取顺序是从右往左的. 先获取了3再是2再是1.
之后是一些操作将这三个参数从内存放到寄存器(Why?), 然后调用sum函数.
sum函数的汇编代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
mov QWORD PTR [ rbp - 0x18 ], rdi
mov QWORD PTR [ rbp - 0x20 ], rsi
mov QWORD PTR [ rbp - 0x28 ], rdx
mov DWORD PTR [ rbp - 0x4 ], 0x0
mov rax , QWORD PTR [ rbp - 0x18 ]
mov edx , DWORD PTR [ rax ]
mov rax , QWORD PTR [ rbp - 0x20 ]
mov eax , DWORD PTR [ rax ]
add edx , eax
mov rax , QWORD PTR [ rbp - 0x28 ]
mov eax , DWORD PTR [ rax ]
add eax , edx
mov DWORD PTR [ rbp - 0x4 ], eax
mov eax , DWORD PTR [ rbp - 0x4 ]
首先是从寄存器取值, 放到内存, 然后进入函数, 执行函数内部的操作, 最后将计算结果从内存放到寄存器. 这里注意一下型参的顺序.
rdi, rsi, rdx对应的内存分别是a, b, c.
所以, 可以对上面的demo, 可以知道其内存分布是
从低地址到高地址, 型参按照从左往右的顺序, 函数体按照从上往下的顺序执行;
函数本体
从低地址到高地址, 实参按照从右往左的顺序, 函数体按照从上往下的顺序执行;
函数调用
内存对齐
源码头文件中,注意一下这个宏,内存对齐作用 看这里 :
1
2
#define __va_rounded_size(TYPE) \
(((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))
TYPE size >= 4,偏移量=(sizeof(TYPE) / 4) * 4
TYPE size < 4, 偏移量=4
所以是按4Byte,32位对齐。
va_list
仅是一个指针, 这是一个适用于 va_start()、va_arg() 和 va_end() 这三个宏存储信息的类型。
va_start
将AP指向第一个参数的下一个参数的地址.
1
2
3
4
5
6
7
8
#ifndef __sparc__
#define va_start(AP, LASTARG) \
(AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#else
#define va_start(AP, LASTARG) \
(__builtin_saveregs (), \
AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#endif
va_arg
AP指向下一个参数, 同时返回上一个参数的内容.
1
2
3
#define va_arg(AP, TYPE) \
(AP += __va_rounded_size (TYPE), \
*((TYPE *) (AP - __va_rounded_size (TYPE))))
va_end
将AP指针置空, 做保护用.
1
2
3
#define va_end(AP)
//有些代码中定义为
#define va_end(ap) ( ap = (va_list)0 )
用例
1
2
3
4
5
6
7
8
9
10
11
12
int sum ( int count , ...)
{
va_list vl ;
int sum = 0 ;
va_start ( vl , count );
for ( int i = 0 ; i < count ; ++ i )
{
sum += va_arg ( vl , int );
}
va_end ( vl );
return sum ;
}
结合开头讲述的内存分布就不难理解, va_list
是一个指针, 型参都是在连续内存中的.
va_start(vl, count)
的时候, 指向了count的下一个指针(count地址, 加上count的size).
va_arg(vl, int)
的时候, 先是将vl指向下一个地址, 然后再返回上一个地址的值.