zoukankan      html  css  js  c++  java
  • C语言可变参数的原理和应用

    来源:微信公众号「编程学习基地」

    2021年的第二篇文章,C语言可变参数

    概述

    C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦;

    即使采用C++,如果参数个数不能确定,也很难采用函数重载.对这种情况,有些人采用指针参数来解决问题

    var_list可变参数介绍

    VA_LIST 是在C语言中解决变参问题的一组宏,原型:

    typedef char* va_list;
    

    其实就是个char*类型变量

    除了var_list ,我们还需要几个宏来实现可变参数

    va_start、va_arg、va_end

    #define _INTSIZEOF(n)   ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
    #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )//第一个可选参数地址
    #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )//下一个参数地址
    #define va_end(ap)    ( ap = (va_list)0 )                  // 将指针置为无效
    

    简单使用可变参数

    #include <stdio.h>
    #include <stdarg.h>
    int AveInt(int, ...);
    void main()
    {
        printf("%d	", AveInt(2, 2, 3));
        printf("%d	", AveInt(4, 2, 4, 6, 8));
        return;
    }
    
    int AveInt(int v, ...)
    {
        int ReturnValue = 0;
        int i = v;
        va_list ap;
        va_start(ap, v);
        while (i > 0)
        {
            ReturnValue += va_arg(ap, int);
            i--;
        }
        va_end(ap);
        return ReturnValue /= v;
    }
    

    啊这..

    可变参数原理

    在进程中,堆栈地址是从高到低分配的.当执行一个函数的时候,将参数列表入栈,压入堆栈的高地址部分,然后入栈函数的返回地址,接着入栈函数的执行代码,这个入栈过程,堆栈地址不断递减,

    黑客就是在堆栈中修改函数返回地址,执行自己的代码来达到执行自己插入的代码段的目的.

    函数在堆栈中的分布情况是:地址从高到低,依次是:函数参数列表,函数返回地址,函数执行代码段.

    说这么多直接上代码演示吧..

    #include <stdio.h>
    #include <stdarg.h>
    int AveInt(int, ...);
    void main()
    {
        printf("AveInt(2, 2, 4): %d
    ", AveInt(2, 2, 4));
        return;
    }
    
    int AveInt(int argc, ...)
    {
        int ReturnValue = 0;
        int next = 0;
        va_list arg_ptr;
    
        va_start(arg_ptr, argc);
        printf("&argc = %p
    ", &argc);            //打印参数i在堆栈中的地址
        printf("arg_ptr = %p
    ", arg_ptr);  //打印va_start之后arg_ptr地址,比参数i的地址高sizeof(int)个字节
        /*  这时arg_ptr指向下一个参数的地址 */
    
        next = *((int*)arg_ptr);
        ReturnValue += next;
    
        next = va_arg(arg_ptr, int);
        printf("arg_ptr = %p
    ", arg_ptr);  //打印va_arg后arg_ptr的地址,比调用va_arg前高sizeof(int)个字节
    
        next = *((int*)arg_ptr);
        ReturnValue += next;
        /*  这时arg_ptr指向下一个参数的地址 */
        va_end(arg_ptr);
        return ReturnValue/argc;
    }
    

    输出:

    &argc = 0088FDD4
    arg_ptr = 0088FDD8
    arg_ptr = 0088FDDC
    AveInt(2, 2, 4): 3
    

    这个是为了介绍简单化,所以举的例子

    这样有点不大方便只能获取两个参数的,用可变参数改变一下

    #include <stdio.h>
    #include <stdarg.h>
    int Arg_ave(int argc, ...);
    void main()
    {
        printf("Arg_ave(2, 2, 4): %d
    ", Arg_ave(2, 2, 4));
        return;
    }
    int Arg_ave(int argc, ...)
    {
        int value = 0;
        int ReturnValue = 0;
    
        va_list arg_ptr;
        va_start(arg_ptr, argc);
        for (int i = 0; i < argc; i++)
        {
            value = va_arg(arg_ptr, int);
            printf("value[%d]=%d
    ", i + 1, value);
            ReturnValue += value;
        }
        return ReturnValue/argc;
    }
    

    输出

    value[1]=2
    value[2]=4
    Arg_ave(2, 2, 4): 3
    

    当你理解之后你就会说就这?这么简单,指定第一个参数是后面参数的总数就可以了,这还不随随便玩

    别着急,精彩的来了,可变参数的应用

    可变参数应用:实现log打印

    #include <stdarg.h>
    #include <stdio.h>
    #include <stdlib.h>
    /*定义一个回调函数指针*/
    typedef void (*libvlcFormattedLogCallback)(void* data, int level, const void* ctx, const char* message);
    enum libvlc_log_level { 
        LIBVLC_DEBUG = 0,       //调试
        LIBVLC_NOTICE = 2,      //普通
        LIBVLC_WARNING = 3,     //警告
        LIBVLC_ERROR = 4 }      //错误
    ;
    /*定义一个回调函数结构体*/
    typedef struct CallbackData {
        void* managedData;
        libvlcFormattedLogCallback managedCallback;
        int minLogLevel;        //log 级别
    } CallbackData;
    
    /*构造回调函数结构体*/
    void* makeCallbackData(libvlcFormattedLogCallback callback, void* data, int minLevel)
    {
        CallbackData* result = (CallbackData *)malloc(sizeof(CallbackData));
        result->managedCallback = callback;
        result->managedData = data;
        result->minLogLevel = minLevel;
        return result;
    }
    
    /*回调函数*/
    void formattedLogCallback(void* data, int level, const void* ctx, const char* message)
    {
        printf("level:%d", level);
        if (level == LIBVLC_ERROR)
        {
            printf("LIBVLC_ERROR:%s", message);
            return;
        }
        if (level >= LIBVLC_WARNING) {
            printf("LIBVLC_WARNING:%s", message);
            return;
        }
        if (level >= LIBVLC_NOTICE)
        {
            printf("LIBVLC_ERROR:%s", message);
            return;
        }
        if (level >= LIBVLC_DEBUG) {
            printf("LIBVLC_WARNING:%s", message);
            return;
        }
        
        
    }
    
    /*和石化log信息并执行回调函数*/
    void InteropCallback(void* data, int level, const void* ctx, const char* fmt, va_list args)
    {
        CallbackData* callbackData = (CallbackData*)data;
        if (level >= callbackData->minLogLevel)
        {
            va_list argsCopy;
            int length = 0;
    
            va_copy(argsCopy, args);
            length = vsnprintf(NULL, 0, fmt, argsCopy);
            va_end(argsCopy);
    
            char* str = malloc(length + 1);
            if (str != NULL)
            {
                va_copy(argsCopy, args);
                vsprintf(str, fmt, argsCopy);
                va_end(argsCopy);
            }
            else
            {
                // Failed to allocate log message, drop it.
                return;
            }
            callbackData->managedCallback(callbackData->managedData, level, ctx, str);
            free(str);
        }
    }
    void sendLog(void* data, int level, const void* ctx, const char* fmt, ...)
    {
        va_list va;
        va_start(va, fmt);
        InteropCallback(data, level, ctx, fmt, va);
        va_end(va);
    }
    int main(int argc, char** argv)
    {
        /*注册一个回调函数结构体,level等级为LIBVLC_WARNING 只要发送的log等级大于等于LIBVLC_WARNING次啊会触发回调函数*/
        void* callbackData = makeCallbackData(formattedLogCallback, "context", LIBVLC_WARNING);
        /*发送四个等级的消息*/
        sendLog(callbackData, LIBVLC_DEBUG, NULL, "This should not be displayed : %s
    ","debug");
        sendLog(callbackData, LIBVLC_NOTICE, NULL, "This should not be displayed : %s
    ", "notick");
        sendLog(callbackData, LIBVLC_WARNING, NULL, "This message level is : %s
    ", "warning");
        sendLog(callbackData, LIBVLC_ERROR, NULL, "Hello, %s ! You should see %ld message here : %s
    ", "World", 1, "warning message");
    
        free(callbackData);
        return 0;
    }
    

    输出

    level:3LIBVLC_WARNING:This message level is : warning
    level:4LIBVLC_ERROR:Hello, World ! You should see 1 message here : warning message
    

    这个使用示例精妙之处在于注册一个指定level的回调函数makeCallbackData(formattedLogCallback, "context", LIBVLC_WARNING);

    然后在发送log的时候根据level判断是否执行回调函数,顺便格式化log信息

  • 相关阅读:
    python2文件转换为exe可执行文件
    pycharm下 os.system os.popen执行命令返回有中文乱码
    python 虚拟环境
    git commit之后,想撤销commit
    Android Dialog使用举例
    Builder模式在Java中的应用(转)
    AngularJS promise()
    给你一个承诺
    AngularJs 用户登录验证模块(demo)参考总结
    推荐 15 个 Angular.js 应用扩展指令(参考应用)
  • 原文地址:https://www.cnblogs.com/deroy/p/14387620.html
Copyright © 2011-2022 走看看