• 《C标准库》——之<stdarg.h>


    C语言有个很强大的功能,依靠它,实现了printf等这类有着变长参数列表的函数或者宏。它就是在<stdarg.h>里的变长参数。


    内容:

    va_list   :它是一个适合保存va_start、va_arg和va_end所需要的信息的类型。
    va_start  :void va_start(va_list ap, parmN); 要在访问所有未命名的参数之前调用宏va_start,宏va_start对ap进行初始化,以便va_arg和va_end使用。
    va_arg    :type va_arg(va_list, type); 它展开是一个表达式,这个表达式的类型和值跟调用的下一个参数的相同。参数ap应该和va_start初始化的va_list ap相同。
    va_end    :如果没有执行va_start,或者在返回之前没有调用va_end,那么这种行为未定义。


    实现:


    还要用到我们在<stddef.h>中提到的<yvals.h>,它里面有两个宏:_AUPBND和_ADNBND

       #define _AUPBND (sizeof (acpi_native_int) - 1) //acpi_native_int 为4字节(32位)(根据机子字数而定)
       #define _ADNBND (sizeof (acpi_native_int) - 1) 
    typedef char* va_list; // 因此,va_list实际上表示的是char*
    #define va_start(ap, A)  (void)((ap) = (char*)&(A) + _Bnd(A, _AUPBND))
    #define va_arg(ap, T)    (*(T*) (((ap) += _Bnd(T, _AUPBND)) - _Bnd(T, _ADNBND)))
    #define va_end(ap)       (void)0
    
    #define _Bnd(X, bnd)    (sizeof (X) + (bnd) &~(bnd) )




    在实现里我们看到,va_list其实是char*类型,va_start将ap移动到参数列表的首地址,为va_arg的实现做准备,va_end啥都没干。

    而va_arg是技巧性最强的一个。首先通过增加va_list对象的内容来使它跳向下一个参数空间的起始位置,然后然退回来指向当前参数的起始位置,然后通过强制类型住哪换把这个指针转换为指定类型的指针,最后解引用获得这个指针以访问存储在数据对象中的值。


    在va_start和va_arg里,都用到了一个_Bnd(T, _AUPBND)或_Bnd(T, _ADNBND),由_AUPBND和_ADNBND的宏定义我们知道,它们总是等于在指定的机器中int类型的字节数减1。

    事实上, _Bnd宏做的事情就是字节对齐,C语言函数入栈都是字节对齐的,都是所在机器上int类型字节的整数倍进行压栈,这样做可以是内存高效的访问。

    sizeof(X)先计算出X类型的大小(假设为s),(bnd) & ~(bnd) 其实bnd就是_AUPBND和_ADNBND(我们假设它们都为3),先将X的大小加3,加了3就会清掉s的二进制数的最低两位,做个假设,假设s为7:00000111,然后加3:00000011,就变为00001010,最后&3的反:(11111100),最后_Bnd计算的结果就是00001000,为8,是int类型大小的整数倍,实现了字节对齐。









  • 相关阅读:
    ISC DHCP: Enterprise grade solution for configuration needs
    The most widely used name server software: BIND
    不是技术牛人,如何拿到国内IT巨头的Offer--转
    NVIDIA---CUDA
    BIOS
    Computer form factor
    OC-常见错误 方法与函数的区别
    OC-面向对象
    OC-基本
    C-结构体、枚举
  • 原文地址:https://www.cnblogs.com/averson/p/5096081.html
Copyright © 2020-2023  润新知