zoukankan      html  css  js  c++  java
  • C里面的变长参数

    C里面的变长参数

    原文来自个人博客(求访问/关注/收藏): https://bbing.com.cn/ cnblog个人博客不定期转载

    stdarg.h

    这里用到的是stdarg.h这个库, 可以在C语言里面实现可变长参数.

    当然C++会简单得多, C++11之后的模板原生支持可变长参数.

    几个函数va_list、va_start、va_arg、va_end,定义在stdarg.h

    内存结构

    先需要理解C/C++函数入参的顺序.

    按照以下的demo, 将其翻译成汇编代码.

    #include <iostream>
    using namespace std;
    
    int sum(const int &a, const int &b, const int &c)
    {
        int d = 0;
        d = a + b + c;
        return d;
    }
    
    int main()
    {
        int s = sum(1, 2, 3);
        cout << s << endl;
    
        return 1;
    }
    

    首先是main函数, 主体部分的汇编

    mov    DWORD PTR [rbp-0x10],0x3
    mov    DWORD PTR [rbp-0xc],0x2
    mov    DWORD PTR [rbp-0x8],0x1
    lea    rdx,[rbp-0x10]
    lea    rcx,[rbp-0xc]
    lea    rax,[rbp-0x8]
    mov    rsi,rcx
    mov    rdi,rax
    call   401172 <sum(int const&, int const&, int const&)>
    

    可以看到, main函数调用了sum函数, 首先搜获取三个参数, 1, 2, 3; 获取顺序是从右往左的. 先获取了3再是2再是1.

    之后是一些操作将这三个参数从内存放到寄存器(Why?), 然后调用sum函数.

    sum函数的汇编代码如下

    mov    QWORD PTR [rbp-0x18],rdi
    mov    QWORD PTR [rbp-0x20],rsi
    mov    QWORD PTR [rbp-0x28],rdx
    mov    DWORD PTR [rbp-0x4],0x0
    mov    rax,QWORD PTR [rbp-0x18]
    mov    edx,DWORD PTR [rax]
    mov    rax,QWORD PTR [rbp-0x20]
    mov    eax,DWORD PTR [rax]
    add    edx,eax
    mov    rax,QWORD PTR [rbp-0x28]
    mov    eax,DWORD PTR [rax]
    add    eax,edx
    mov    DWORD PTR [rbp-0x4],eax
    mov    eax,DWORD PTR [rbp-0x4]
    

    首先是从寄存器取值, 放到内存, 然后进入函数, 执行函数内部的操作, 最后将计算结果从内存放到寄存器. 这里注意一下型参的顺序.
    rdi, rsi, rdx对应的内存分别是a, b, c.

    所以, 可以对上面的demo, 可以知道其内存分布是

    • 对函数本体:

    从低地址到高地址, 型参按照从左往右的顺序, 函数体按照从上往下的顺序执行;

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Aa7wKQB7-1611797623781)(https://s3.ax1x.com/2021/01/27/szNKCF.png "函数本体")]

    • 对函数调用:

    从低地址到高地址, 实参按照从右往左的顺序, 函数体按照从上往下的顺序执行;

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-62CNYxtK-1611797623782)(https://s3.ax1x.com/2021/01/27/szNM34.png "函数调用")]

    内存对齐

    源码头文件中,注意一下这个宏,内存对齐作用 看这里

    #define __va_rounded_size(TYPE)  
      (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))
    
    1. TYPE size >= 4,偏移量=(sizeof(TYPE) / 4) * 4
    2. TYPE size < 4, 偏移量=4

    所以是按4Byte,32位对齐。

    va_list

    typedef char *va_list;
    

    仅是一个指针, 这是一个适用于 va_start()、va_arg() 和 va_end() 这三个宏存储信息的类型。

    va_start

    将AP指向第一个参数的下一个参数的地址.

    #ifndef __sparc__
    #define va_start(AP, LASTARG)                                           
     (AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
    #else
    #define va_start(AP, LASTARG)                                           
     (__builtin_saveregs (),                                                
      AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
    #endif
    

    va_arg

    AP指向下一个参数, 同时返回上一个参数的内容.

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

    va_end

    将AP指针置空, 做保护用.

    #define va_end(AP)
    //有些代码中定义为
    #define va_end(ap)      ( ap = (va_list)0 )
    

    用例

    int sum(int count, ...)
    {
        va_list vl;
        int sum = 0;
        va_start(vl, count);
        for (int i = 0; i < count; ++i)
        {
            sum += va_arg(vl, int);
        }
        va_end(vl);
        return sum;
    }
    

    结合开头讲述的内存分布就不难理解, va_list是一个指针, 型参都是在连续内存中的.

    va_start(vl, count)的时候, 指向了count的下一个指针(count地址, 加上count的size).

    va_arg(vl, int)的时候, 先是将vl指向下一个地址, 然后再返回上一个地址的值.

    分享自由,尊重著作权
  • 相关阅读:
    python—虚拟环境搭建
    pytnon—线程,进程
    python——新excel模块之openpyxl
    装饰器——应用
    css样式
    HTML
    广图登陆知网下载资源教程
    使用k-近邻算法改进约会网站的配对效果
    k-近邻算法概述
    机器学习基础
  • 原文地址:https://www.cnblogs.com/jerry323/p/14338098.html
Copyright © 2011-2022 走看看