C语言/C++学习之C语言可变参数的原理和应用
小职 2021-01-07 来源 :「编程学习基地 」,此处仅用来学习分享 阅读 734 评论 0

摘要:本文主要向大家介绍了C语言/C++学习之C语言可变参数的原理和应用,通过具体的内容向大家展现,希望对大家C语言/C++的学习有所帮助。

本文主要向大家介绍了C语言/C++学习之C语言可变参数的原理和应用,通过具体的内容向大家展现,希望对大家C语言/C++的学习有所帮助。

C语言/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信息



关注“职坐标在线”(Zhizuobiao_Online)公众号,免费获取学习视频资料、技术就业咨询

C语言/C++学习之C语言可变参数的原理和应用

本文由 @小职 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程