> 文章列表 > 【深度解刨C语言】可变参数列表

【深度解刨C语言】可变参数列表

【深度解刨C语言】可变参数列表

文章目录

  • 前言
  • 一.可变参数列表
    • 1.基本认识
    • 2.基本使用
      • 1.一个类型重定义
      • 2.三个宏
        • 前提
      • 1.__crt_va_start
      • 2. __crt_va_arg
      • 3.__crt_va_end

前言

如果你对函数栈帧不了解,建议先去补栈帧这一块知识,本文涉及到这部分知识。
函数栈帧
本文利用函数栈帧的结论

  • 函数传参压栈从右向左,进行压栈。
  • 传参时,并没有调用函数
  • 比整形小的数据类型压栈时会提升为整形

一.可变参数列表

1.基本认识

符号:…(三个小数点)
前提:可变参数列表作为函数参数,前必须至少有一个实参。

找最大值的函数声明与定义:

int Max(int nums, ...);//可变参数列表的前面至少有一个实参
int Max(int nums, ...)
{va_list arg;__crt_va_start(arg, nums);int max = __crt_va_arg(arg, int);for (int i = 1; i < nums; i++){int ret = __crt_va_arg(arg, int);if (max < ret){max = ret;}}__crt_va_end(arg);return max;
}

2.基本使用

涉及到四个量——一个类型重定义与三个宏

1.一个类型重定义

va_list arg;
  • va_list实际上是char *的类型重定义
    如下:
typedef char* va_list;

2.三个宏

前提

  • _INTSIZEOF宏的理解

简单理解:
对类型大小向4对齐

例:_INTSIZEOF(char),char类型大小不足4,向4看齐,结果为4
例:_INTSIZEOF(int),int类型大小为4,向4看齐,结果还为4。

实现原理:
简单的来说分为两种情况:

  1. 为4的倍数——int ,long long int。
    向4看齐:
  • 等于数据类型大小
  1. 小于4的——char,short.
    向4看齐:
  • 等于(数据类型大小/4+1)*4

将两种情况合并在一起:

  • 向4对齐的倍数 * 4

向4对齐的倍数
对于第一种情况来说:
  等于数据类型大小/4
对于第二种情况来说:
 等于数据类型大小/4+1

问题就转换:将4对齐的倍数的情况合并:
数据类型大小都可看做
能被4整除的部分(记作n)+不能被4整除的部分(记作r):
问题继续转换:将不能整除4的部分转化成能被4整除的部分
因为r大于等于1,并且小于等于3
所以r最大加上3即可转换成能被4整除的部分
所以数据类型大小可看做:
n+r+3
因此:向4对齐的倍数——(n+r+3)/4
因为:n+r是数据类型的大小
因此:又可记作:(数据类型大小+3)/4
因此:向4对齐即为:(数据类型大小+3)/4*4
又因为在计算机中整除4和乘4意为将后两位清0
因此又可以写作:((数据类型大小+3)>>2)<<2
既然这样我们又可以利用**&的性质:同1才为1**
3按位取反,再逻辑与上数据类型大小+3
这里的3不就是sizeof(int)-1吗?,数据类型大小不就是sizeof(数据类型)
因此又可以写作:(sizeof(数据类型)+sizeof(int)-1)&(~(sizeof(int)-1))
又因为~的优先级高于&
因此又可以写作:(sizeof(数据类型)+sizeof(int)-1)& ~(sizeof(int)-1)
因此可以理解定义中做的事情:
【深度解刨C语言】可变参数列表

1.__crt_va_start

__crt_va_start
//定义: #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
//_ADDRESSOF(v) (&(v))
//代换可得:(void)(ap=(char *)(&(v))+ _INTSIZEOF(v))
//代入上面找最大值的代码__crt_va_start(arg, nums);
//(void)(arg=(char *)(&(nums))+ _INTSIZEOF(nums))
//也就是将arg赋值为nums地址+nums的数据类型向4的对齐数
//换句话说:arg赋值为可变参数的第一个参数的地址

功能:通过函数的可变参数的倒数第一个参数找到可变参数的第一个参数的地址
原理:函数栈帧
参数1:va_list的变量
参数2:可变参数列表的正数第一个参数

2. __crt_va_arg

参数1:va_list的变量
参数2:数据类型

#define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
//这里面的括号有点多拆开来看
//从里往外分析:(ap += _INTSIZEOF(t)) - _INTSIZEOF(t)
//第一个式子:(ap += _INTSIZEOF(t))将ap加等上t的4字节对齐数
//注意:此时改变了ap的值
//第二个式子:- _INTSIZEOF(t),对第一个式子的结果减这个值
//注意:表达式的结果并没有发生变化,但是ap却发生变化了,指向了下一个元素
//第三个式子*(t*),对表达式的结果强转,再解引用访问到t类型大小的的值

功能:获取可变参数里面的变量的值,并指向可变参数下一个变量。

3.__crt_va_end

参数:va_list的变量

//定义:
#define __crt_va_end(ap)        ((void)(ap = (va_list)0))

功能:将变量置为空,防止其为野指针。