语言基础 /CC++ 可变参函数设计与实践,va_ 系列实战详解(强制参数和变参数的参数类型陷阱)

文章目录

  • 概述
  • va_ 系列定义
  • va_list 类型
  • va_start 宏
    • 从变参函数的强制参数谈起
    • 宏 va_start 对 char 和 short 类型编译告警
    • 宏 va_start 源码分析
    • 猜测 __va_start 函数实现
  • va_arg 宏
    • 宏 va_arg 无法接受 char 和 short
    • 为啥va_arg可解析int却不能解析float类型?
    • 宏 va_arg 源码解析
    • 变参数类型: sizeof(结构体) > sizeof(_int64)
  • va_end 宏
  • 小结

概述

本文基于丰富的可变参函数设计、实现、使用实践经验,进一步结合对 va_list、va_start 、va_arg、va_end 源码分析,剖析了变参列表的处理过程,重新回答和解释了变参函数实现和使用过程中遇到的诸多问题和陷阱,如,
为什么 va_start 宏函数不愿接受 char或short 类型的参数变量?
为什么 va_arg 宏函数不可接受 char或short 等类型名?
实践过 int、long是可以做变参类型的,那么, float、double 可以做变参类型吗?
为什么 va_arg 宏函数可以正确解析 int 类型,却不肯接受 float 类型?
实践过结构体指针类型做变参类型,那么,结构体对象类型可做变参类型吗?

@History
上述问题已埋在CSDN草稿中太久太久。
@关联
@《语言基础 /C&C++ 可变参函数设计与实践,必须要指定可变参数的个数?》
@关联
@《语言基础 /C&C++ 可变参函数设计与实践,变参函数的实现、使用、替代方法》

转载请标明原文链接,
https://blog.csdn.net/quguanxin/category_6223029.html

va_ 系列定义

在 C/C++ 语言中,提供了一组以 va_ 为前缀的宏和类型定义,用以在函数内部迭代访问可变数量的参数,可称它们为 “va_list系列” ,它们的定义位于stdarg.h头文件中。一个简短的介绍如下:
va_list类型:
va_list 是一个用于存储可变参数信息的类型,通常是一个指向内部结构的指针,在可变参数函数中使用va_list类型的变量来迭代访问参数列表。
va_start宏:
va_start 宏用于初始化 va_list 类型的变量,以开始对参数列表的访问。它接受两个参数,第一个是 va_list 类型的变量,第二个是可变参数函数中最后一个具名参数的名称,它会根据具名参数的位置和大小来计算参数列表的起始位置。
va_arg宏:
va_arg 宏用于获取参数列表中的下一个参数值。它接受两个参数,第一个是 va_list 类型的变量,第二个是要获取的参数的类型,它会返回指定类型的下一个参数值,并将 va_list 变量 ap 更新为指向下一个参数的位置。
va_end宏:
va_end 宏用于结束对参数列表的访问。它接受一个va_list 类型的变量,它会执行一些清理操作,以确保参数列表的访问结束。

早些年定义和实现可变参函数时,使用 mingW 编译环境,
在这里插入图片描述
如上,并不能进行更深层次的源码跳转,更没去研究过mingW工具集相关组件的源码。而在 MSVC 下,可查看详细定义,

在 Microsoft Visual Studio 14.0\VC\include\stdarg.h 其顶层定义为,
在这里插入图片描述
相关的_crt_va_ 宏可进一步跳转代码到 vadefs.h 查看详细定义,
-/-/-
在这里插入图片描述
-/-/-
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如上,除了 _M_X64 模式下的 va_start 宏定义,不能查阅全源码,其他情况下的定义,算是完整的。后文将逐一展开讲解。

va_list 类型

在可变参函数实践过程中,已经认识到,类型 va_list 定义如下,
在这里插入图片描述
如上,分别是在VS2017、MingW7.3、百科给出的 va_list 类型定义,它们定义在各自版本的 vadefs.h 这个C语言标准库头文件中。故,在大部分情况下,va_list 类型就是一个char指针类型,这是下文中理解一些现象的关键因素。

还要纠正之前的一个错误,

int __CRTDECL  printf(char const* const format, ...);
int __CRTDECL vprintf(const char* format, va_list argptr);

函数 printf 是变参函数,但是 vprintf 不是变参函数,因为 va_list 是一个具体的类型,argptr 是具名参数。

va_start 宏

在变参函数的实践中,最早遇到的问题是 va_start 相关的告警,本节便从它开始谈起。先了解强制参数的定义,再细细研究为什么强制参数是 char 或 short 类型时,会有编译警告。

从变参函数的强制参数谈起

在代表变参列表的语法符号… 前,可以存在多个确定的参数,其中,紧挨着…的那个确定参数,也即最后一个确定(具名)参数,一般称为变参函数的强制参数,它是必须存在的,或者说是至少要存在的。在《语言基础 /C&C++ 可变参函数设计与实践,必须要指定可变参数的个数?YES》 文中,我们实际验证了 va_start 宏函数是不接收 NULL参数的,至少在QtCreator+MSVC环境下那会导致编译器崩溃,也证明了把 ‘变参数个数’ 作为变参函数强制参数是不错的选择。

void print_Integers2(int param_count, ...) {
    va_list argptr;
    va_start(argptr, param_count); 
    for (int i = 0; i < param_count; i++) {
        int value = va_arg(argptr, int);
        //do something .., or save first..
        qDebug("test2_Param%d:%d ", i+1, value);  
    }
    va_end(argptr); 
}

宏 va_start 对 char 和 short 类型编译告警

宏函数 va_start 不仅不接受 null,它也不愿意接受short 和 char 类型的参数。以前的草稿记录中有写下过:char和short等类型不可以作为可变参函数的强制参数类型,注意不是说指针类型哈。这种说法倒是没有什么很大的错误,只是不太确切,我们应该更加直接将此类问题安置在 va_start 和 va_arg 宏函数本身上来继续讨论。

最早的相关记录,是在某嵌入式项目中,Keil下C编程,遇到编译告警如下,
在这里插入图片描述
其他类似告警如下,只有简单记录,已找不到出处,未再设法复现,

//warning:  #1256-D: "MR_U8" would have been promoted to "int" when passed through the ellipsis parameter; use the latter type instead

还有一次类似的记录,当时没写IDE类型,后来在 QtCreator+MSVC2015 下复现,如下,
在这里插入图片描述

va_start(argptr, count);
//warning: passing an object that undergoes default argument promotion to 'va_start' has undefined behavior
//note: parameter of type 'short' is declared here`

要说明的一点是,虽然上述函数在 va_start 处存在编译告警,但上述函数是运行正常的。在VS集成开发环境下,同样测试上述 print_Integers3 函数实现,没有告警。
在这里插入图片描述

再后来,在一些机缘巧合之下,我发现:QtCreator + MSVC2015 集成开发环境下,va_start 告警提示,以及下文中提到的 va_arg 参数类型告警提示、对 va_arg 取地址操作时的错误提示,竟然是可以控制打开和关闭的。在开启 QtCreator 插件配置 ClangCodeModel 的情况下,通过 Clang 的检测机制,上述异常会报告,否则,没有任何提示。虽然上述告警并不会产生实质性的错误,但我还是深追了下来,毕竟前期被它吊了很久。

宏 va_start 源码分析

宏函数 va_start 可以接受 int、long 类型的参数,但却不太愿接受 unsigned char、char、short类型,这是为啥?所以,总想着找点理论上的支撑。接下来的文章中,我们将在QtCreator + MSVC2015_64 环境下,完成相关探究过程。

一开始我的研究方向有点跑偏,
由于上文告警中的,default argument promotion,其含义是默认参数提升。它是C语言中的一种隐式类型转换规则,用于将函数调用中的较小的整数类型和浮点类型参数提升为较大的整数类型和浮点类型。在《C语言程序设计》第2版 2.7 类型转换一节中,有提到,
在这里插入图片描述
在这里插入图片描述
但总觉得有些说不通,
以上文 print_Integers2(short param_count, …) 函数为例,这里的 param_count 是一个具参数,它不属于变参列表,它是明确声明的,这里也没有多元操作符,也不是什么无函数原型的情况,它怎么能被提升呢?
后来,得知,
根据C语言标准,可变参数的传递方式要求参数按照一定的字节对齐方式进行压栈。这种对齐方式要求参数的大小必须是特定字节的倍数。对于 va_start 宏的第二个参数,该具名参数的类型决定了可变参数列表的起始位置和对齐方式。由于 char和short类型的大小不能直接满足对齐要求,因此会告警。
再后来,
透过 _M_IX86 下 __crt_va_start_a 中使用的 _INTSIZEOF(n) 定义,也可以证明了上述猜测,

    #define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
    #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))

计算过程1,sizeof(int) - 1:
这样做是为了确保后续的对齐操作不会超过 int 类型的大小。
计算过程2,(sizeof(n) + sizeof(int) - 1):
在上一过程基础上,得到一个比类型 n 大的值。
计算过程3,~(sizeof(int) - 1):
用于生成一个掩码,将最后 sizeof(int) 个比特位设置为 1,其余比特位设置为 0。
((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)):
将过程2和过程3的结果进行按位与操作,得到 sizeof(n) 的倍数并对齐到 int 类型的边界

最后,
这里倒是可以借用上文《C语言程序设计》中的那句话。对于可变参数函数中的强制参数(即形参类表中最后一个确定的参数),即使你的实参为char或short等,也请把它声明为int或long类型。这可能是个好习惯。
另外要注意的是,在C语言中,va_start 宏并不会直接申请可变参数列表的空间,当然 va_arg 也不会。宏 va_start 的主要作用是初始化一个 va_list 类型的变量,以便在函数内部访问可变参数。实际上可变参数列表的空间是由编译器自动管理的,在函数调用时,编译器会根据函数原型中的参数信息和函数调用时提供的实际参数,来为可变参数列表分配合适的内存空间。

猜测 __va_start 函数实现

//大河qu // vadefs.h 
#define __crt_va_start(ap, x) ((void)(__vcrt_va_start_verify_argument_type<decltype(x)>(), __crt_va_start_a(ap, x)))
//其中__vcrt_va_start_verify_argument_type其内容是静态断言,旨在强制"va_start参数不能具有引用类型,也不能用括号括起来"

//大河qu 
#elif defined _M_X64
    void __cdecl __va_start(va_list* , ...);
    #define __crt_va_start_a(ap, x) ((void)(__va_start(&ap, x)))
	...

前文提到过,在 _M_X64 模式下的 va_start 宏定义,不能查阅全源码,那么,就来猜测下它的实现过程。(纯属以前的自己闲的蛋疼)

void __cdecl __va_start(va_list* ap, ...) {
    // 获取可变参数函数中最后一个具名参数的地址
    void* last_arg = &ap;
    // 根据具名参数的地址计算可变参数列表的起始地址
    *ap = (va_list)last_arg + sizeof(void*);
    // 将起始地址按照平台相关的规则对齐 //如8字节
    *ap = (va_list)(((uintptr_t)(*ap) + 7) & ~7);
}

为此还设计了一个测试用例,

void print_Integers7(char param_count, /*real param is int */...) {
    qDebug("test_int7_forcedparam_addr:0x%.16llx, value:%d ", (int64_t)&param_count, param_count);
    va_list argptr;
    va_start(argptr, param_count);
    for (int i = 0; i < param_count; i++) {
        int *pvalue = &va_arg(argptr, int);
        qDebug("test_int7_param%d_addr:0x%.16llx, value:%d ", i+1, (int64_t)pvalue, *pvalue);
    }
    va_end(argptr);
}

在这里插入图片描述
如上结果显示,即使传递了 char 类型给 va_start 宏,变参函数的地址也会被设置为8字节对齐。这里还有个插曲:在 QtCreator 软件,开启 ClangCodeModel 插件选项时, &va_arg(argptr, int) 操作并标识为错误代码,提示,
在这里插入图片描述
类似异常的分析,已经记录在其他文章中,此处不再讨论。只了解到,某些编译器可能提供一些扩展或非标准功能,允许对右值进行取地址操作,如未开启 ClangCodeModel 的QtCreator,如 未特殊配置过的 Visual Studio…

va_arg 宏

前文我们已经贴出了,在 MSVC 2015 下的完整的 va_arg 宏函数定义。对 va_arg 宏的研究始于,那段 “想方设法不不给变参函数传递参数个数信息” 的尴尬历程,再后来,读懂了 va_arg 在 MSVC 源码定义,一切才迎刃而解。

宏 va_arg 无法接受 char 和 short

在Keil中,
在这里插入图片描述
在 QtCreator + MSVC2015 中,

int value = va_arg(argptr, short);
//warning: second argument to 'va_arg' is of promotable type 'short'; this va_arg has undefined behavior because arguments will be promoted to 'int'
//stdarg.h:35:50: note: expanded from macro 'va_arg'

与 va_start 差不多,va_arg 宏函数也不待见 char 和 short 类型。不同的是,前者针对这种情况,会执行默认提升,不会造成大问题,而后者,诚不欺你,发生了无法预计的行为,可能会在 va_arg 执行时奔溃掉。所以还是那个建议,请直接使用 int 或 long…

为啥va_arg可解析int却不能解析float类型?

首先验证,va_arg 是否可以解析浮点数,float 类型 或 double 类型,

//执行失败/结果不符合预期
void print_float1(int param_count, /*real param is float*/...) {
    va_list argptr;
    va_start(argptr, param_count);     
    for (int i = 0; i < param_count; i++) {
        float value = va_arg(argptr, float);  //note float
        qDebug("test_float1_param%d:%2.3f ", i+1, value);
    }
    va_end(argptr);
}
//执行成功/结果符合预期
void print_float2(int param_count, /*real param is float*/...) {
    va_list argptr;
    va_start(argptr, param_count);   
    for (int i = 0; i < param_count; i++) {
        double value = va_arg(argptr, double);  //note double
        qDebug("test_float2_param%d:%2.3f ", i+1, value);
    }
    va_end(argptr);
}

int main() {
	//8, 4, 4, 8
    qDebug("sizeof(__int64):%d, sizeof(int):%d, sizeof(float):%d, sizeof(double):%d", sizeof(__int64), sizeof(int), sizeof(float), sizeof(double));  
	//
    print_float1(3, 1.234, 1.345, 1.567);
	//
    print_float2(3, 2.234, 2.345, 2.567);
	...
}

在这里插入图片描述
如上,使用 va_arg 解析 float 类型的操作,失败了。那么,就产生了一个新问题,结合在 x64下 va_arg 定义,无论是int或float类型,它们都是4字节,实际运行中,其都将执行定义中的第二个分支。但,va_arg 可以正确解析 int 类型,却不能正确解析 float 类型,这是什么鬼?

//__crt_va_arg(ap, t)   //in defined _M_X64
 *(t* )((ap += sizeof(__int64)) - sizeof(__int64)))

结合上文,我们设计并调试查看 va_arg 宏返回值 value 的地址信息,

//
void print_Integers7(int param_count, /*real param is int */...) {
    va_list argptr;
    va_start(argptr, param_count);
    for (int i = 0; i < param_count; i++) {
        int *pvalue = &va_arg(argptr, int);
        qDebug("test_int7_param%d_addr:0x%.16llx, value:%d ", i+1, (int64_t)pvalue, *pvalue);
    }
    va_end(argptr);
}
//
void print_float7(int param_count, /*real param is float*/...) {
    va_list argptr;
    va_start(argptr, param_count);
    for (int i = 0; i < param_count; i++) {
        float *pvalue = &va_arg(argptr, float);
        qDebug("test_float7_param%d_addr:0x%.16llx, value:%2.3f ", i+1, (int64_t)pvalue, *pvalue);
    }
    va_end(argptr);
}

在这里插入图片描述
再细细品鉴 va_arg 的源码定义,无论是int还是float类型,指针偏移都按照8字节的sizeof(int64)来执行,也可以想到,int和float数据类型在调用变参函数的那一刻,已经分别被默认的提升为 int64和double类型。再回到float为啥不行的问题上,其实这是一个常规错误,常见于新手代码。即"指针类型强转并解引用"非常危险。函数 print_float7 实际上执行了,用 float* 类型 去强转 double* 类型,然后在接下来的解引用操作中,用float去解析double的内存,这肯定是错误的。一个干净的例子如下,

int main() {
	//
    double dvalue = 1.2345;
    float fResult = *((float*)&dvalue);
    //float value from double by float pointer:0.00000
    qDebug("float value from double by float pointer:%2.5f ", fResult);

    int64_t i64value = 12345;
    int iResult = *((int*)&i64value);
    //int value from int64 by int pointer:12345 
    qDebug("int value from int64 by int pointer:%d ", iResult);
    
    return 0;
}

在这里插入图片描述
解引用操作具有一定的风险,如果解引用的指针无效或未初始化,或者指针指向的内存位置不符合指针类型的要求,那么解引用操作可能会导致未定义的行为。在 IEEE 754 标准中,double 类型的内存布局按照以下顺序排列:
在这里插入图片描述
而单精度浮点数float,按照上述布局,分别占用1-8-23bit位。由于 float和double 数据类型的内存构造的上述差异,按照float类型去解引用一个实际存储double数值的内存地址,自然是不符合规则的。对于一个正整数,int指针强转int64指针类型,由于整数数据的内存构造,在数值32bit大小以内的情况下,碰巧不会有问题,但这也是应该被禁止的操作。

宏 va_arg 源码解析

宏 va_arg 的主要作用是使得我们能够在函数中按照参数的类型和顺序获取变长参数列表中的参数值,从而实现对可变数量参数的处理。下文枚举了 _M_IX86 和 _M_X64 两种模式下的 va_arg 定义,前者容易理解,后者可能要费点小心思。

_M_IX86

//Microsoft Visual Studio 14.0\VC\include\vadefs.h
#elif defined _M_IX86
    #define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
    #define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))

理解它的关键是,+=操作是对ap自身进行修改的,而减法操作是不会的。故,+=操作的目的是为了改变ap指针,使得其指向下一个参数,而(变更后的)ap 减 _INTSIZEOF(t) 是为了访问当前参数的指针。
(ap += _INTSIZEOF(t)):
ap 指针会根据参数类型的大小 _INTSIZEOF(t) 进行偏移,以便指向下一个参数的内存位置。
(ap - _INTSIZEOF(t)):
ap 指针会减去参数类型 t 的大小,以便回到当前参数的内存位置。如果你没有理解+=会改变自身的特性,那么这可能看上去很诡异。因为ap 已经被 va_start 初始化过,已经指向了第一个变参的地址,因此下一步直接解引用就可以得到第一个参数,但是大神程序员为了少写一行递增指针的代码…
((t)((ap - _INTSIZEOF(t))):
通过将指针转换为指定类型 t 的指针,并通过解引用操作 * 来获取参数的值。

_M_X64

#elif defined _M_X64
    #define __crt_va_arg(ap, t)                                               \
        ((sizeof(t) > sizeof(__int64) || (sizeof(t) & (sizeof(t) - 1)) != 0) \
            ? **(t**)((ap += sizeof(__int64)) - sizeof(__int64))             \
            :  *(t* )((ap += sizeof(__int64)) - sizeof(__int64)))
...

条件 (sizeof(t) & (sizeof(t) - 1)) != 0 的含义是检查参数类型 t 的大小是否是2的幂次方。
补充,
在计算机中,2的幂次方的二进制表示形式是只有一个位为1,其他位都为0。因此,如果 sizeof(t) 是2的幂次方,那么 sizeof(t) - 1 的二进制表示形式将会是所有位都为1。而按位与运算符 & 将两个操作数的对应位进行逻辑与操作,只有在对应的位都为1时,结果位才为1。
因此,如果 (sizeof(t) & (sizeof(t) - 1)) 的结果等于0,那么说明 sizeof(t) 是2的幂次方,即参数类型 t 的大小是2的幂次方。
回到源码分析上,
(sizeof(t) > sizeof(__int64) || (sizeof(t) & (sizeof(t) - 1)) != 0
不等于 0,即表示为 true。即条件1或条件2,其中之一为true即可。
也就是说,如果参数类型的大小大于 sizeof(__int64) 或者 参数类型的大小不是2的幂次方值,也即不是1、2、4、8 等2的幂次方值,不是char、short、int、long 等,如一些单字节对齐的结构体。此时执行分支1。
否则,也就是,参数类型的大小 <= sizeof(__int64) 且大小是2的幂次方值,也即是 char、short、int、long 等单数据类型。将执行第2个条件分支。

对于第一个分支,其使用双指针将 ap 指针转换为指向指针类型 t** 的指针,然后通过两次解引用操作 ** 来获取指针指向的值,即参数的值,其应该是主要针对结构体类型的。对于非结构体类型,包含指针类型,统一的以 sizeof(__int64) 字节长度来处理。结构体类型的参数可能具有不同的大小和内存布局,因此需要特殊处理,相当于是把一个不确定的长度转换成了一个确定的长度,否则没有信息来执行地址的偏移。通过对指针进行一次解引用操作 ,我们得到指向 t* 类型的指针,以此可访问结构体参数的地址。再次对指针进行解引用操作 ,可以获取指针指向的值,即结构体类型的参数。

其他特别需要注意的是,
实践和理论上都可表明,va_arg 并不能按照预期识别出实参列表的结尾。这也是《语言基础 /C&C++ 可变参函数设计与实践,必须要指定可变参数的个数?YES》 重点要表达的。

变参数类型: sizeof(结构体) > sizeof(_int64)

基于对 va_arg 宏函数的理解,结构体对象参数理论上是可以被变参函数解析过程 va_arg 接受的,至少在 x64 平台下是有可能的。先在 QtCreator + MSVC2015_64 环境下测试,

//测试1
typedef struct tagParamA {
    short iSeg1;
    short iSeg2;
} TParamA;

//测试2
typedef struct tagParamB {
    short iSeg1;
    short iSeg2;
    int   a;
} TParamB;

//结构体尺寸必须要大于U64
typedef struct tagParamC {
    short iSeg1;
    short iSeg2;
    int   a;
    short b;
} TParamC;

//
void TestStructParamaB(int param_count, /* param is TParamA/B */...) {
    va_list argptr;
    va_start(argptr, param_count);
    for (int i = 0; i < param_count; i++) {
        TParamB value = va_arg(argptr, TParamB);  //note TParamB
        qDebug("test_struct_param%d:(%d,%d,%d,%d) ", i+1, value.iSeg1, value.iSeg2, value.a);
    }
    va_end(argptr);
}

//
void TestStructParamaC(int param_count, /* param is TParamC */...) {
    va_list argptr;
    va_start(argptr, param_count);
    for (int i = 0; i < param_count; i++) {
        TParamC value = va_arg(argptr, TParamC);  //note TParamC
        qDebug("test_struct_param%d:(%d,%d,%d,%d) ", i+1, value.iSeg1, value.iSeg2, value.a, value.b);
    }
    va_end(argptr);
}


int main()  {
	//sizeof(TParamA)---4, sizeof(TParamB)---8, sizeof(TParamC) ---12
	//
	//TParamB tArray[10] = {{100, 101, 102}, {200, 201, 202}, 0};
	//TestStructParamaB(2, &tArray[0], &tArray[1]);
	//
    TParamC tArray[10] = {{100, 101, 102, 103}, {200, 201, 202, 203}, 0};
    TestStructParamaC(2, &tArray[0], &tArray[1]);
    /*system("pause");*/ return 0;
}

Test B
在这里插入图片描述
Test C
在这里插入图片描述
如上在X64平台下,经过测试 TParamA 和 TParamB 对象直接作为变参函数的解析类型和实参类型时,变参函数不能输出正确的结果,只有当结构体的尺寸大小大于 sizeof(__int64) 字节后,相关函数实现和运行过程才是正确的。
将同样的程序配置成x86平台,编译运行(VS 2017 x86配置),
在这里插入图片描述
如上,在Windows X86平台配置下,结构体对象是不能直接被 va_arg 解析的。

以 TParamA/B为例,其实现函数中,执行的是 __crt_va_arg 宏定义的第二个分支,代入,

*(TParamA *)((ap += 8) - 8))  //这个错误很好理解
*(TParamB *)((ap += 8) - 8))  //这个我没有理解透彻

TParamA的情况下,会导致指针偏移错误,进而解析混乱,这并不难理解。但是 TParamB 的测试结果,我并没有理解清楚,不明白它为啥会输出错误结果。

//VS2017 X64 
void TestStructParamaC(int param_count, /* param is TParamC */...)
{
    printf("test_struct_fixparam:(Addr:0x%.16llx)\r\n ", (int64_t)(&param_count));
    va_list argptr;
    va_start(argptr, param_count);
    for (int i = 0; i < param_count; i++) {
        TParamC *pValue = &va_arg(argptr, TParamC);  //note TParamC
        printf("test_struct_param%d(Addr:0x%.16llx, &Addr:0x%.16llx) \r\n", i + 1, (int64_t)pValue, (int64_t)(&pValue));
        printf("test_struct_param%d:(%d,%d,%d,%d) \r\n", i + 1, pValue->iSeg1, pValue->iSeg2, pValue->a, pValue->b);
    }
    va_end(argptr);
}

在这里插入图片描述
通过如上结果,可分析得到,当使用结构体对象直接做可变参数时,其初始地址并不是具名参数偏移sizeof(_int64),这应该与 __va_start(va_list* ap, …) 函数的实际行为有关。我没有多余时间继续研究下去,不过至此,我们最初的目的都已经达到,就这样吧。

va_end 宏

    #define __crt_va_end(ap)        ((void)(ap = (va_list)0))

略。

小结

先这样吧!不总结了。有问题请留言。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/609864.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis之ResultMap

前言 select语句查询得到的结果集是一张二维表&#xff0c;水平方向上看是一个个字段&#xff0c;垂直方向上看是一条条记录。而Java是面向对象的程序设计语言&#xff0c;对象是根据类定义创建的&#xff0c;类之间的引用关 系可以认为是嵌套的结构。在JDBC编程中&#xff0c…

PyTorch中定义自己的数据集

文章目录 1. 简介2. 查看PyTorch自带的数据集(可视化)3. 准备材料3.1 图片数据3.2 标签数据 4. 方法 1. 简介 尽管PyTorch提供了许多自带的数据集&#xff0c;如MNIST、CIFAR-10、ImageNet等&#xff0c;但它们对于没有经验的用户来说&#xff0c;理解数据加载器的工作原理以及…

【数据结构】栈的实现以及数组和链表的优缺点

个人主页&#xff1a;一代… 个人专栏&#xff1a;数据结构 1.栈 1.1栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进…

批量自定义重命名,一键添加顺序编号,文件夹管理更高效!

我们经常需要对文件夹进行管理和整理。然而&#xff0c;当面对大量需要改名的文件夹时&#xff0c;手动逐个修改不仅效率低下&#xff0c;还容易出错。那么&#xff0c;有没有一种方法能够批量自定义重命名文件夹&#xff0c;并在名称后自动添加顺序编号呢&#xff1f;答案是肯…

C++反汇编——多态,面试题01

文章目录 1.C的三大特性1.1封装1.2继承1.3多态1.3.1 虚函数1.3.2 多态代码反汇编分析。反汇编分析1——基类指针指向子类对象&#xff0c;构造过程。反汇编分析2——基类指针指向子类对象&#xff0c;调用虚函数getPrice()过程。反汇编分析3——基类对象&#xff0c;调用虚函数…

版本控制工具之Git的基础使用教程

Git Git是一个分布式版本控制系统&#xff0c;由Linux之父Linus Torvalds 开发。它既可以用来管理和追踪计算机文件的变化&#xff0c;也是开发者协作编写代码的工具。 本文将介绍 Git 的基础原理、用法、操作等内容。 一、基础概念 1.1 版本控制系统 版本控制系统&#x…

PSoc™62开发板之IoT应用

实验目的 使用PSoc62™开发板驱动OLED模块&#xff0c;实时监控室内的光照强度、温度信息 实验准备 PSoc62™开发板SSD1309 OLED模块DS18B20温度传感器BH1750光照传感器 模块电路 SSD1309 OLED模块的电路连接和模块配置教程请参考之前的文章&#xff0c;这里不详细展开描…

汽车EDI:IAC Elmdon EDI 对接指南

近期收到客户C公司的需求&#xff0c;需要与其合作伙伴IAC Elmdon建立EDI连接&#xff0c;本文将主要为大家介绍IAC Elmdon EDI 对接指南&#xff0c;了解EDI项目的对接流程。 项目需求 传输协议&#xff1a;OFTP2 IAC Elmdon 与其供应商之间使用的传输协议为OFTP2。OFTP2是…

云南区块链商户平台优化开发

背景 云南区块链商户平台是全省统一区块链服务平台。依托于云南省发改委、阿里云及蚂蚁区块链的国内首个省级区块链平台——云南省区块链平台同步上线&#xff0c;助力数字云南整体升级。 网页版并不适合妈妈那辈人使用&#xff0c;没有记忆功能&#xff0c;于是打算自己开发…

科技查新中医学科研项目查新点如何确立与提炼?案例讲解

一、前言 医学科技查新包括立项查新和成果查新两个部分&#xff0c;其中医学立项查新&#xff0c;它是指在医学科研项目申报开题之前&#xff0c;通过在一定范围内进行该课题的相关文献检索 ( 可以根据项目委托人的具体要求&#xff0c;进行国内检索或者进行国外检索 ) &#x…

媲美Suno、Udio!AI铁了心,要砸音乐人的饭碗

5月10日凌晨&#xff0c;著名语音生成式AI平台ElevenLabs在社交平台宣布&#xff0c;推出文本生成歌曲产品ElevenLabs Music。 从其展示的效果来看&#xff0c;音乐的节奏感、和声、乐器的搭配、情感表达、创意性、风格的多样性、高/低音&#xff0c;可媲美该领域的两款头部产…

k8s StatefulSet

Statefulset 一个 Statefulset 创建的每个pod都有一个从零开始的顺序索引&#xff0c;这个会体现在 pod 的名称和主机名上&#xff0c;同样还会体现在 pod 对应的固定存储上。这些 pod 的名称是可预知的&#xff0c;它是由 Statefulset 的名称加该实例的顺序索引值组成的。不同…

【元对象系统概述】

元对象系统概述 &#x1f31f; 元对象&#x1f31f; 元对象系统&#x1f31f; QT官方文档中给出的定义&#x1f31f;《Qt5.9 C开发指南》中给出的定义 &#x1f31f; 元对象 元对象是一个描述类的信息的数据结构&#xff0c;在qt中常常与QObject的类相关联。 可以通过QObject::…

这些企业注意!推荐使用OVSSL证书

JoySSL官网 注册码230918 SSL证书作为一种重要的安全措施&#xff0c;对于确保网站数据传输的安全性至关重要。而在众多SSL证书类型中&#xff0c;OV&#xff08;Organization Validation&#xff0c;组织验证&#xff09;SSL证书以其独特的功能和适用范围&#xff0c;成为众多…

夸克网盘免费扩容N次20T的方法

上文我们用&#xff1a;夸克网盘免费领取1TB空间的方法使自己的网盘扩容到1TB&#xff0c;但只有三个月还不够大。 所以用下面的方法那个免费的把自己的网盘扩容到20TB。 一、 登录任推邦 APP 需要借助这个平台&#xff0c;这是夸克网盘的第三方服务商&#xff0c;完善注册信…

2024年自动驾驶、车辆工程与智能交通国际会议(ICADVEIT2024)

2024年自动驾驶、车辆工程与智能交通国际会议&#xff08;ICADVEIT2024&#xff09; 会议简介 2024年自动驾驶、车辆工程和智能交通国际会议&#xff08;ICADVEIT 2024&#xff09;将在中国深圳举行。会议主要聚焦自动驾驶、车辆工程和智能交通等研究领域&#xff0c;旨在为从…

智慧便民小程序源码系统 求职招聘+房产出租+相亲交友 带完整的安装代码包以及系统搭建教程

在数字化、智能化的今天&#xff0c;我们的生活节奏越来越快&#xff0c;对于各种服务的需求也越发多元化和个性化。为了满足广大市民对于便捷、高效、全面的服务需求&#xff0c;罗峰给大家分享一款智慧便民小程序源码系统&#xff0c;集求职招聘、房产出租、相亲交友三大功能…

【全开源】Java U U跑腿同城跑腿小程序源码快递代取帮买帮送源码小程序+H 5+公众号跑腿系统

特色功能&#xff1a; 智能定位与路线规划&#xff1a;UU跑腿小程序能够利用定位技术&#xff0c;为用户提供附近的跑腿服务&#xff0c;并自动规划最佳路线&#xff0c;提高配送效率。订单管理&#xff1a;包括订单查询、订单状态更新、订单评价等功能&#xff0c;全行业覆盖…

Mac YOLO V9本地训练(命令行模式)

环境&#xff1a; Mac M1 (MacOS Sonoma 14.3.1) Python 3.11PyTorch 2.1.2 一、YOLO v9工程及模型准备 详见&#xff1a;Mac YOLO V9推理测试-CSDN博客 二、数据集准备 Roboflow Universe上有许多小规模的数据集&#xff0c;很适合用来进行目标检测。 首先安装依赖 pip …

NVIDIA 配置 Jetson 扩展针座

系列文章目录 前言 每个 Jetson 开发套件包括多个扩展接头和连接器&#xff08;统称 "接头"&#xff09;&#xff1a; 40 针扩展接头&#xff1a; 可让您将 Jetson 开发套件连接到现成的 Raspberry Pi HAT&#xff08;顶部附加硬件&#xff09;&#xff0c;如 Seee…
最新文章