zoukankan      html  css  js  c++  java
  • c语言可变参数函数

    c语言支持可变参数函数。这里的可变指,函数的参数个数可变。

    其原理是,一般情况下,函数参数传递时,其压栈顺序是从右向左,栈在虚拟内存中的增长方向是从上往下。所以,对于一个函数调用 func(int a, int b, int c); 如果知道了参数a的地址,那么,可以推导出b,c的地址

    #include <stdio.h>
    
    
    void test(int a, int b, int c)
    {
        printf("%p, %p, %p
    ", &a, &b, &c);
    }
    
    
    int sum(int n, ...)
    {
        int * p = &n;
        int s = 0;
    
        for (int i = 0; i < n; i++)
        {
            s += *(++p);
        }
    
        return s;
    }
    
    
    int main()
    {
        test(1,3,4);
        printf("sum = %d
    ", sum(3,4,5,6));
    
        return 0;
    }

    对于上面的代码,

    suse10 32位,运行结果:

        0xbfc7d700, 0xbfc7d704, 0xbfc7d708
        sum = 15

    vs2013 64位,运行结果:

        0046FBBC, 0046FBC0, 0046FBC4
        sum = 15

    分析这两个结果,可以发现,test函数参数地址递增,相邻的差值是4。类似,所以sum函数,可以正确执行。

    ubuntu 18.04 64位系统,test函数地址也是递增,相邻的差值是4。但是,sum函数并不能正确执行。分析其汇编代码后,发现,参数n后边紧跟的4字节并不是下一个参数的地址。下面三个参数地址相对于n的偏移分别是 -0xa8, -0xa0, -0x98。这和该版本ubuntu内核有关系吧。所以,sum函数也就无效了。

    操作可变参数的宏

    针对可变参数,系统提供了va_arg宏。man 3 va_arg可以看到文档。

    具体包括

           #include <stdarg.h>
    
           void va_start(va_list ap, last);
           type va_arg(va_list ap, type);
           void va_end(va_list ap);
           void va_copy(va_list dest, va_list src);
    stdarg.h 文件在 /usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h

    在linux下,宏的定义如下:

    #define va_start(v,l)   __builtin_va_start(v,l)
    #define va_end(v)   __builtin_va_end(v)
    #define va_arg(v,l) __builtin_va_arg(v,l)
    #define va_copy(d,s)    __builtin_va_copy(d,s)

    这是使用gcc内建的定义了,尴尬。到此打住,不深究了。

    我们来看看vs2013的定义:

    stdarg.h:
        #define va_start _crt_va_start
        #define va_arg _crt_va_arg
        #define va_end _crt_va_end
        
    vadef.h:
        #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )  // 内存按照4字节对齐
    
        #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )    // 找到第一个参数的地址
        #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )  // 获取地址中的值。并移动pa指针
        #define _crt_va_end(ap)      ( ap = (va_list)0 )                   // 指针赋空

    有了系统提供的宏,sum函数可以改写了:

    int sum(int n, ...)
    {
        va_list ap;
        int s = 0;
    
        va_start(ap, n);
    
        for (int i = 0; i < n; i++)
        {
            s += va_arg(ap, int);
        }
    
        return s;
    }

    这个函数可以在ubuntu 18.04 64位正确运行了。

    printf函数

    第一次遇到可变参数函数,就是这个printf函数了。懂了原理之后,我们可以写一个简单的:

    #include <unistd.h>
    #include <stdarg.h>
    
    
    int print(const char * fmt, ...)
    {
        char szbuf[2048] = {0};
        char *p = szbuf;
        va_list ap;
    
        va_start(ap, fmt);
      // 简单做了一下判断,不严谨
    while (*fmt && p < szbuf) { if ('%' == *fmt) { ++fmt; switch (*fmt++) { case '%': *p++ = '%'; break; case 'c': *p++ = va_arg(ap, int); break; case 'd': { int num = va_arg(ap, int); char sztmp[40] = {0}; char *tmp = sztmp; while (num) { *++tmp = num % 10 + '0'; num /= 10; } while (tmp != sztmp) { *p++ = *tmp--; } break; } case 's': { char * tmp = va_arg(ap, char*); while (*tmp) { *p++ = *tmp++; } break; } } } else { *p++ = *fmt++; } } int len = p - szbuf; write(0, szbuf, len); va_end(ap); return len; } int main() { print("hello "); int ret = print("%d, %d ", 82,87635); print("ret = %d ", ret); print("zhe shi yige jieguo ret = %d, per = %%%d ", 23, 98); return 0; }
  • 相关阅读:
    Linux Shell脚本启动jar、关闭jar
    SpringBoot基于切面来拦截@PathVariable参数及抛出异常全局处理方法
    SpringBoot引用font awesome不显示问题的解决
    解决RestTemplate请求url出现301转发错误 301 Moved Permanently
    npm报错:Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 10.x
    npm 安装 chromedriver 失败的解决办法
    npm run dev报错 JS stacktrace(Node内存溢出)
    Mysql批量修改表字段名称为小写
    Ubuntu18 apt更换国内源 加快下载速度
    微信小程序如何实现支付宝支付?
  • 原文地址:https://www.cnblogs.com/zuofaqi/p/9858903.html
Copyright © 2011-2022 走看看