聊聊C++中的可变参数

发布时间:2023年12月18日

一、一个可变参的经典函数 printf

... 用来表示可变参数,我们看看一个经典的函数原型:

_Check_return_opt_
    _CRT_STDIO_INLINE int __CRTDECL printf(
        _In_z_ _Printf_format_string_ char const* const _Format,
        ...)
    #if defined _NO_CRT_STDIO_INLINE
    ;
    #else
    {
        int _Result;
        va_list _ArgList;
        __crt_va_start(_ArgList, _Format);
        _Result = _vfprintf_l(stdout, _Format, NULL, _ArgList);
        __crt_va_end(_ArgList);
        return _Result;
    }
    #endif

1.1 拓展讲讲

先稍微展开知识点。printf 函数的定义可以说非常的标准

_Check_return_opt_

#if defined _PREFAST_ && defined _CA_SHOULD_CHECK_RETURN
    #define _Check_return_opt_ _Check_return_
#else
    #define _Check_return_opt_
#endif

#include <sal.h>
// Check the return value of a function e.g. _Check_return_ ErrorCode Foo();
#define _Check_return_           _SAL2_Source_(_Check_return_, (), _Check_return_impl_)

看到这里就明白,原来也是sal里用于返回值检查

_CRT_STDIO_INLINE

#if defined _NO_CRT_STDIO_INLINE
    #undef _CRT_STDIO_INLINE
    #define _CRT_STDIO_INLINE
#elif !defined _CRT_STDIO_INLINE
    #define _CRT_STDIO_INLINE __inline
#endif

原来就是内联函数的关键字重命名了。

__CRTDECL

#define __CRTDECL __CLRCALL_PURE_OR_CDECL

#ifdef _M_CEE_PURE
    #define __CLRCALL_PURE_OR_CDECL __clrcall
#else
    #define __CLRCALL_PURE_OR_CDECL __cdecl
#endif

库的导出,也没啥

_In_z_

// nullterminated 'in' parameters.
// e.g. void CopyStr( _In_z_ const char* szFrom, _Out_z_cap_(cchTo) char* szTo, size_t cchTo );
#define _In_z_                          _SAL2_Source_(_In_z_, (),     _In_     _Pre1_impl_(__zterm_impl))
#define _In_opt_z_                      _SAL2_Source_(_In_opt_z_, (), _In_opt_ _Pre1_impl_(__zterm_impl))

sal中对于入参的检测,必须是非空

你看看,你看看,我在不久前介绍说sal是真的非常经典吧(不过无人问津😅)。更多关于sal的介绍可以回看 《使用Sal减少代码缺陷 :你不必欣赏的源代码注释语言之美》

1.2 简化

int  printf(char const* const _Format,...)
{
        int _Result;
        va_list _ArgList;
        __crt_va_start(_ArgList, _Format);
        _Result = _vfprintf_l(stdout, _Format, NULL, _ArgList);
        __crt_va_end(_ArgList);
        return _Result;
 }

有感觉熟悉而又陌生的朋友不,别想太复杂。va_list是一个类型,用于存储可变参数的信息。va_start宏用于初始化va_list。va_arg宏用于获取下一个参数。va_end宏用于清理va_list。

注意,使用va_list,va_start,va_arg和va_end宏处理可变参数时,你需要提供一个方式来确定参数的数量和类型。

那么,我这么讲是胡说么,肯定是有依据的,我们看看下面的源码部分:

#if defined _M_CEE_PURE || (defined _M_CEE && !defined _M_ARM && !defined _M_ARM64)

    void  __cdecl __va_start(va_list*, ...);
    void* __cdecl __va_arg(va_list*, ...);
    void  __cdecl __va_end(va_list*);

    #define __crt_va_start_a(ap, v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
    #define __crt_va_arg(ap, t)     (*(t *)__va_arg(&ap, _SLOTSIZEOF(t), _APALIGN(t,ap), (t*)0))
    #define __crt_va_end(ap)        ((void)(__va_end(&ap)))

#elif defined _M_IX86 && !defined _M_HYBRID_X86_ARM64

/***********************************咱们重点来看看这里*****************************************/
    #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)))
    #define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
    #define __crt_va_end(ap)        ((void)(ap = (va_list)0))
/********************************************************************************************/
#elif defined _M_ARM

    #ifdef __cplusplus
        void __cdecl __va_start(va_list*, ...);
        #define __crt_va_start_a(ap, v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), _ADDRESSOF(v))))
    #else
        #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _SLOTSIZEOF(v)))
    #endif

    #define __crt_va_arg(ap, t) (*(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t)))
    #define __crt_va_end(ap)    ((void)(ap = (va_list)0))

#elif defined _M_HYBRID_X86_ARM64
    void __cdecl __va_start(va_list*, ...);
    #define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
    #define __crt_va_arg(ap, t)    (*(t*)((ap += _SLOTSIZEOF(t)) - _SLOTSIZEOF(t)))
    #define __crt_va_end(ap)       ((void)(ap = (va_list)0))

#elif defined _M_ARM64

    void __cdecl __va_start(va_list*, ...);

    #define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
    #define __crt_va_arg(ap, t)                                                 \
       ((sizeof(t) > (2 * sizeof(__int64)))                                   \
           ? **(t**)((ap += sizeof(__int64)) - sizeof(__int64))               \
           : *(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t)))
    #define __crt_va_end(ap)       ((void)(ap = (va_list)0))

#elif defined _M_ARM64EC
    void __cdecl __va_start(va_list*, ...);
    //take the ARM64 va_start (for now)
    #define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
    //a hybrid va arg, to account for the shift in calling convention, with the alignment of ARM64
    #define __crt_va_arg(ap, t)                                               \
        ((sizeof(t) > sizeof(__int64) || (sizeof(t) & (sizeof(t) - 1)) != 0) \
            ? **(t**)((ap += sizeof(__int64)) - sizeof(__int64))             \
            : *(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t)))
    #define __crt_va_end(ap)        ((void)(ap = (va_list)0))

#elif defined _M_X64

    void __cdecl __va_start(va_list* , ...);

    #define __crt_va_start_a(ap, x) ((void)(__va_start(&ap, x)))
    #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)))
    #define __crt_va_end(ap)        ((void)(ap = (va_list)0))

#endif

这一段定义了在不同的系统架构下,__crt_va_start_a __crt_va_arg__crt_va_end的具体实现方式。
我们仅以下面为例分析。

/***********************************咱们重点来看看这里*****************************************/
    #ifndef _VA_LIST_DEFINED
    #define _VA_LIST_DEFINED
    #ifdef _M_CEE_PURE
        typedef System::ArgIterator va_list;
    #else
        typedef char* va_list; //here
    #endif
    #endif
   
	#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)))
    #define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
    #define __crt_va_end(ap)        ((void)(ap = (va_list)0))
/********************************************************************************************/
int  printf(char const* const _Format,...)
{
        int _Result;
        va_list _ArgList; // ==》 先等价看作 char* _ArgList;
        __crt_va_start(_ArgList, _Format);  // 指针指向参数列表的首地址
        _Result = _vfprintf_l(stdout, _Format, NULL, _ArgList); // 循环遍历输出参数到标准输出
        __crt_va_end(_ArgList);  // 指针指向null
        return _Result;
 }

_vfprintf_l 函数原型如下,具体实现咱么就不在此一层层扒拉下去了。


    _Check_return_opt_
    _CRT_STDIO_INLINE int __CRTDECL _vfprintf_l(
        _Inout_  FILE*       const _Stream,
        _In_z_   char const* const _Format,
        _In_opt_ _locale_t   const _Locale,
                 va_list           _ArgList
        )
    #if defined _NO_CRT_STDIO_INLINE
    ;
    #else
    {
        return __stdio_common_vfprintf(_CRT_INTERNAL_LOCAL_PRINTF_OPTIONS, _Stream, _Format, _Locale, _ArgList);
    }
    #endif

二、实现可变参数的打印输出函数

2.1 使用va_list

#include <cstdarg>
template<typename _Ty>
void print(_Ty count, ...) {
    va_list args;
    va_start(args, count);

    for (int i = 0; i < count; ++i) {
        int value = va_arg(args, int);
        std::cout << value << " ";
    }

    va_end(args);
    std::cout << std::endl;
}
// eg
  	const char* p = "jellt";
    const char arr[] = {"this is a girl"};
    print<int>(2,sizeof(p),sizeof(arr));

2.2 使用c++可变参数模板实现

// 基本情况:只有一个参数
template<typename T>
void Mycout(const T& t) {
    std::cout << t << std::endl;
}

// 递归情况:有多个参数
template<typename T, typename... Args>
void Mycout(const T& t, const Args&... args) {
    std::cout << t << ", ";
    Mycout(args...);  // 递归调用,处理剩余的参数
}

在C++中,... 是一个可变参数包,它可以包含任意数量的参数。Args... 表示一个类型参数包,args... 表示一个非类型参数包。

在函数模板中,你可以使用 … 来定义一个接受任意数量参数的函数。然后,你可以使用 args… 来访问这些参数。例如,你可以将 args… 传递给其他函数,或者在函数体中遍历 args…。

在你的例子中,Mycout(args...) 是一个递归调用。它将 args... 展开,并将展开后的参数传递给 Mycout 函数。这个过程会一直进行,直到 args... 中只有一个参数为止。这时,编译器会选择一个参数的 Mycout 函数(你需要提供这个函数,否则会导致编译错误)。

这个过程是在编译时期完成的。编译器会为每个不同的参数列表生成一个不同的函数实例。这就是为什么我们说可变参数模板可以接受任意数量的参数:编译器会为我们生成所有需要的函数。

    const char* p = "jellt";
    const char arr[] = {"this is a girl"};
    Mycout<int>(sizeof(p),sizeof(arr));
文章来源:https://blog.csdn.net/weixin_39568531/article/details/135058049
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。