关于参数处理那点事,C标准库反汇编解析

关于参数处理那点事,C标准库反汇编解析

在这里插入图片描述

1 stdarg.h 内容概览

这个头文件用于提供访问无名参数(既没有命名也没有类型)的类型和宏。

假设函数形如:

void functionWithMltipleInput(normalType n, ...)

第一个参数名为n,后续省略号表示有不定量个类型未知的参数。

为了在函数中处理这些参数,我们需要一组方法来获取无名参数的值。

stdarg.h提供了1个类型和4个函数式的宏来满足上述需求。可以说这个头文件相当简单了。

  • va_list 变量参数列表(指针),用于存放参数的信息
  • va_start 初始化变量参数列表的宏
  • va_arg 用于获取下一个参数的宏
  • va_end 用于表明结束使用va_list的宏
  • va_copy 复制变量参数列表

va_start()

void va_start (va_list ap, paramN);

初始化变量ap, 使其指向第一个无名参数

  • ap 未初始化的va_list,实质上是一个char*类型的指针。
  • paramN 最后一个显示参数(named parameter)的变量名

va_arg()

type va_arg (va_list ap, type)

将p指向的参数解析为type类型,同时修改ap,使其指向参数列表中的下一个参数。

也就是说,初始化ap后连续两次调用va_arg,第一次返回第一个无名参数的值,第二次返回的就是第二个无名参数的值了。

va_end()

void va_end (va_list ap);

要求只要使用了va_start,就必须在退出函数之前调用va_end,不知道为啥,不清楚有啥作用。

va_copy()

没啥用。


example

下面的例程将首先用

va_start(vl, argn);

初始化参数列表指针vl,而后用

val = va_arg(vl,double);

获取下一个参数并将其视为double赋值给val \

src

#include <stdarg.h>
#include <stdio.h>
typedef int normalType;
void minput(normalType argn, ...)
{
    int i;
    int val;
    printf ("Printing unnamed arguments:");
    va_list vl;
    va_start(vl, argn);
    for (i=0;i<argn;i++)
    {
        val=va_arg(vl,int);
        printf(" [%2d]",val);
    }
    va_end(vl);
    printf("\n");
}

int main()
{
    minput(3, 1, 2, 3);
}

output

Printing unnamed arguments: [ 1] [ 2] [ 3]

如下可以看到打印出无名参数的值与调用输入一致。


2 用法反汇编分析

处理

编译

D:\TempWorkSpace\c> gcc .\args.c -o arg.exe

运行

D:\TempWorkSpace\c> .\arg.exe
Printing unnamed arguments: [ 1] [ 2] [ 3]

反汇编

D:\TempWorkSpace\c> objdump -D .\arg.exe > arg.asm

分析

下图可见0x4015c3为main函数的起始地址

00000000004015c3 <main>

运行到0x4015e6处调用函数minput(),跳转到地址0x401550,可见传参3,2,1,3与调用输入一致

callq 401550 <[minput]>

而后在0x4015f5处退出主函数

4015f6 90 retq

在这里插入图片描述

下图可见函数minput()的反汇编结果,函数起始地址为0x401550

图中右侧蓝色高亮部分对应图中左侧函数体中的for循环,可见遍历变量参数列表过程,总共循环三次
在这里插入图片描述

理解

传参意味着将参数值压入栈中,访问参数也就是按参数偏移取值。

va_list就是一个用于取值的指针,使用前用va_start(AP, LASTARG)找到第一个无名变量的基地址,也就是va_list的初始化。

va_arg(AP, TYPE)的作用就是把对应的值设定类型解析出来,操作上类似于*(type*)<va_list>(把va_list当成type类型的指针解引用)。

va_end(va_list)就有点像是注脚了,表明完事了,基本没啥用处,而va_copy()就属于锦上添花,意义不明了。

3 回到标准库源码

标准库通常是宏名下划线预处理乱飞,为了聚焦重点,这里把各种乱七八糟的东西都剔掉了。

#ifndef _STDARG_H
#define _STDARG_H

#ifdef __GNUC__
/* The GNU C-compiler uses its own, but similar varargs mechanism. */
typedef char *va_list;
/* Amount of space required in an argument list for an arg of type TYPE.
TYPE may alternatively be an expression whose type is used. */
#define __va_rounded_size(TYPE)\
	(((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))
#if __GNUC__ < 2
#else    /* __GNUC__ >= */
#ifndef __sparc__
#define va_start(AP, LASTARG)\
	(AP = ((char *) __builtin_next_arg ()))
#else
#endif
void va_end (va_list); /* Defined in libgcc.a */
#define va_end(AP)
#define va_arg(AP, TYPE)\
	(AP = ((char *) (AP)) += __va_rounded_size (TYPE),\
	*((TYPE *) ((char *) (AP) - __va_rounded_size (TYPE))))
#endif    /* __GNUC__ >= */
#else    /* not __GNUC__ */
#endif /* __GNUC__ */
#endif /* _STDARG_H */

可以看到va_start(AP, LASTARG)的实现就是返回一个char*类型的指针,通过__builtin_next_arg获取最后一个显式参数的下一个参数占据的地址。

#define va_start(AP, LASTARG) (AP = ((char *) __builtin_next_arg ()))

va_arg(AP, TYPE)则包含两个操作,修改AP的值

((char *) (AP)) += __va_rounded_size (TYPE)

返回AP当前指向的参数

*((TYPE *) ((char *) (AP) - __va_rounded_size (TYPE)))

拼在一起

#define va_arg(AP, TYPE)                        \
 (AP = ((char *) (AP)) += __va_rounded_size (TYPE),            \
  *((TYPE *) ((char *) (AP) - __va_rounded_size (TYPE))))

va_end(AP)则真的啥也没干,挺好,不用分析了

#define va_end(AP)

4 参考

  1. GNU doc, Varargs.html
  2. Learn.microsoft, c-runtime-library
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值