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
信息