专栏首页编程学习基地C语言可变参数的原理和应用

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\t", AveInt(2, 2, 3));
    printf("%d\t", 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\n", 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\n", &argc);            //打印参数i在堆栈中的地址
    printf("arg_ptr = %p\n", 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\n", 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\n", 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\n", 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\n","debug");
    sendLog(callbackData, LIBVLC_NOTICE, NULL, "This should not be displayed : %s\n", "notick");
    sendLog(callbackData, LIBVLC_WARNING, NULL, "This message level is : %s\n", "warning");
    sendLog(callbackData, LIBVLC_ERROR, NULL, "Hello, %s ! You should see %ld message here : %s\n", "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信息

-- End --

本文分享自微信公众号 - 编程学习基地(LearnBase),作者:deroy

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-01-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【Go 语言社区】Golang 可变参数的使用

    func main() { Func1(1,2,3,4) } func Func1(args ...int) { for _, ...

    李海彬
  • Golang语言--可变参数函数,何时该使用省略号(...)

    今天的一个例子中发现,对于在调用可变参数函数时,不是总能使用省略号将一个切片展开,有时候编译器可能会报错,为了清除的说明这个问题,我用几个小例子一步一步说明。 ...

    李海彬
  • go语言变参,匿名函数的多种用法

    本文为博主原创文章,未经博主允许不得转载。 /** * Created by Administrator on 13-12-18. */ pac...

    李海彬
  • go语言变参,匿名函数的多种用法

    /** * Created by Administrator on 13-12-18. */ package main import ...

    李海彬
  • 存储类型和变量的作用域(C语言)

    在计算机中,用于存程序和数据的物理单元有寄存器和随机存储器(RAM)。寄存器速度快,空间少,常常只存放参加运算的少数变量。RAM比寄存器速度慢,但空间大,可存放...

    ZackSock
  • C语言回调函数的概念及其应用

    打一个简单的例子就是说,如果我们在一个 RTOS 的基础上去编写应用程序,编写应用程序的这一层就是应用层,也可以说是高层,那 RTOS 内核所处的就是内核层,也...

    wenzid
  • 利用C可变参数和宏定义来实现自己的日志系统

    在C语言中,字符串的拼接有很多种方法:memcpy,strcpy,strcat,sprintf等等。

    IOT物联网小镇
  • C语言常用的知识没多少之C语言的数据类型及变量与常量

    C语言中有数值和数制之分,在这里就从数值和数制开始讲起。其实数值和数制这四个字就已经包含了本文的标题C语言的数据类型及变量与常量。

    用户5935416
  • c语言函数指针的理解与使用

    C)这很容易,fun3是函数名,p1,p2是参数,其类型为char *型,函数的返回值为char *类型。 B) 也很简单,与C)表达式相比,唯一不同的就是函数...

    用户7678152

扫码关注云+社区

领取腾讯云代金券