C语言可变参数的原理和应用

开发 后端
C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦;即使采用C++,如果参数个数不能确定,也很难采用函数重载.对这种情况,有些人采用指针参数来解决问题。

 [[373935]]

本文转载自微信公众号「编程学习基地  」,作者deroy  。转载本文请联系编程学习基地  公众号。 

概述

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

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

var_list可变参数介绍

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

  1. typedef char* va_list; 

其实就是个char*类型变量

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

「va_start、va_arg、va_end」

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

简单使用可变参数

  1. #include <stdio.h> 
  2. #include <stdarg.h> 
  3. int AveInt(int, ...); 
  4. void main() 
  5.     printf("%d\t", AveInt(2, 2, 3)); 
  6.     printf("%d\t", AveInt(4, 2, 4, 6, 8)); 
  7.     return
  8.  
  9. int AveInt(int v, ...) 
  10.     int ReturnValue = 0; 
  11.     int i = v; 
  12.     va_list ap; 
  13.     va_start(ap, v); 
  14.     while (i > 0) 
  15.     { 
  16.         ReturnValue += va_arg(ap, int); 
  17.         i--; 
  18.     } 
  19.     va_end(ap); 
  20.     return ReturnValue /= v; 

啊这..

可变参数原理

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

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

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

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

  1. #include <stdio.h> 
  2. #include <stdarg.h> 
  3. int AveInt(int, ...); 
  4. void main() 
  5.     printf("AveInt(2, 2, 4): %d\n", AveInt(2, 2, 4)); 
  6.     return
  7.  
  8. int AveInt(int argc, ...) 
  9.     int ReturnValue = 0; 
  10.     int next = 0; 
  11.     va_list arg_ptr; 
  12.  
  13.     va_start(arg_ptr, argc); 
  14.     printf("&argc = %p\n", &argc);            //打印参数i在堆栈中的地址 
  15.     printf("arg_ptr = %p\n", arg_ptr);  //打印va_start之后arg_ptr地址,比参数i的地址高sizeof(int)个字节 
  16.     /*  这时arg_ptr指向下一个参数的地址 */ 
  17.  
  18.     next = *((int*)arg_ptr); 
  19.     ReturnValue += next
  20.  
  21.     next = va_arg(arg_ptr, int); 
  22.     printf("arg_ptr = %p\n", arg_ptr);  //打印va_arg后arg_ptr的地址,比调用va_arg前高sizeof(int)个字节 
  23.  
  24.     next = *((int*)arg_ptr); 
  25.     ReturnValue += next
  26.     /*  这时arg_ptr指向下一个参数的地址 */ 
  27.     va_end(arg_ptr); 
  28.     return ReturnValue/argc; 

输出:

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

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

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

  1. #include <stdio.h> 
  2. #include <stdarg.h> 
  3. int Arg_ave(int argc, ...); 
  4. void main() 
  5.     printf("Arg_ave(2, 2, 4): %d\n", Arg_ave(2, 2, 4)); 
  6.     return
  7. int Arg_ave(int argc, ...) 
  8.     int value = 0; 
  9.     int ReturnValue = 0; 
  10.  
  11.     va_list arg_ptr; 
  12.     va_start(arg_ptr, argc); 
  13.     for (int i = 0; i < argc; i++) 
  14.     { 
  15.         value = va_arg(arg_ptr, int); 
  16.         printf("value[%d]=%d\n", i + 1, value); 
  17.         ReturnValue += value; 
  18.     } 
  19.     return ReturnValue/argc; 

输出

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

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

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

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

  1. #include <stdarg.h> 
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4. /*定义一个回调函数指针*/ 
  5. typedef void (*libvlcFormattedLogCallback)(void* data, int level, const void* ctx, const char* message); 
  6. enum libvlc_log_level {  
  7.     LIBVLC_DEBUG = 0,       //调试 
  8.     LIBVLC_NOTICE = 2,      //普通 
  9.     LIBVLC_WARNING = 3,     //警告 
  10.     LIBVLC_ERROR = 4 }      //错误 
  11. /*定义一个回调函数结构体*/ 
  12. typedef struct CallbackData { 
  13.     void* managedData; 
  14.     libvlcFormattedLogCallback managedCallback; 
  15.     int minLogLevel;        //log 级别 
  16. } CallbackData; 
  17.  
  18. /*构造回调函数结构体*/ 
  19. void* makeCallbackData(libvlcFormattedLogCallback callback, void* data, int minLevel) 
  20.     CallbackData* result = (CallbackData *)malloc(sizeof(CallbackData)); 
  21.     result->managedCallback = callback; 
  22.     result->managedData = data; 
  23.     result->minLogLevel = minLevel; 
  24.     return result; 
  25.  
  26. /*回调函数*/ 
  27. void formattedLogCallback(void* data, int level, const void* ctx, const char* message) 
  28.     printf("level:%d"level); 
  29.     if (level == LIBVLC_ERROR) 
  30.     { 
  31.         printf("LIBVLC_ERROR:%s", message); 
  32.         return
  33.     } 
  34.     if (level >= LIBVLC_WARNING) { 
  35.         printf("LIBVLC_WARNING:%s", message); 
  36.         return
  37.     } 
  38.     if (level >= LIBVLC_NOTICE) 
  39.     { 
  40.         printf("LIBVLC_ERROR:%s", message); 
  41.         return
  42.     } 
  43.     if (level >= LIBVLC_DEBUG) { 
  44.         printf("LIBVLC_WARNING:%s", message); 
  45.         return
  46.     } 
  47.      
  48.      
  49.  
  50. /*和石化log信息并执行回调函数*/ 
  51. void InteropCallback(void* data, int level, const void* ctx, const char* fmt, va_list args) 
  52.     CallbackData* callbackData = (CallbackData*)data; 
  53.     if (level >= callbackData->minLogLevel) 
  54.     { 
  55.         va_list argsCopy; 
  56.         int length = 0; 
  57.  
  58.         va_copy(argsCopy, args); 
  59.         length = vsnprintf(NULL, 0, fmt, argsCopy); 
  60.         va_end(argsCopy); 
  61.  
  62.         char* str = malloc(length + 1); 
  63.         if (str != NULL
  64.         { 
  65.             va_copy(argsCopy, args); 
  66.             vsprintf(str, fmt, argsCopy); 
  67.             va_end(argsCopy); 
  68.         } 
  69.         else 
  70.         { 
  71.             // Failed to allocate log message, drop it. 
  72.             return
  73.         } 
  74.         callbackData->managedCallback(callbackData->managedData, level, ctx, str); 
  75.         free(str); 
  76.     } 
  77. void sendLog(void* data, int level, const void* ctx, const char* fmt, ...) 
  78.     va_list va; 
  79.     va_start(va, fmt); 
  80.     InteropCallback(data, level, ctx, fmt, va); 
  81.     va_end(va); 
  82. int main(int argc, char** argv) 
  83.     /*注册一个回调函数结构体,level等级为LIBVLC_WARNING 只要发送的log等级大于等于LIBVLC_WARNING次啊会触发回调函数*/ 
  84.     void* callbackData = makeCallbackData(formattedLogCallback, "context", LIBVLC_WARNING); 
  85.     /*发送四个等级的消息*/ 
  86.     sendLog(callbackData, LIBVLC_DEBUG, NULL"This should not be displayed : %s\n","debug"); 
  87.     sendLog(callbackData, LIBVLC_NOTICE, NULL"This should not be displayed : %s\n""notick"); 
  88.     sendLog(callbackData, LIBVLC_WARNING, NULL"This message level is : %s\n""warning"); 
  89.     sendLog(callbackData, LIBVLC_ERROR, NULL"Hello, %s ! You should see %ld message here : %s\n""World", 1, "warning message"); 
  90.  
  91.     free(callbackData); 
  92.     return 0; 

输出                                                                                                                                                                                                                

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

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

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

 

责任编辑:武晓燕 来源: 编程学习基地
相关推荐

2011-05-13 17:25:34

C

2012-09-18 13:26:39

CC++

2009-06-29 15:23:00

2011-08-01 17:11:43

Objective-C 函数

2010-02-03 15:06:02

C++可变参数表

2023-12-04 18:31:59

C语言函数

2022-07-14 16:35:11

C语言编程语言

2024-01-17 06:23:35

SwiftTypeScript定义函数

2010-01-15 18:50:37

C++语言

2017-04-11 08:36:09

iOS编译应用

2022-07-01 11:56:54

C语言C++编程语言

2011-03-30 11:01:13

C语言随机

2022-01-17 07:32:34

Java参数方法

2011-06-15 10:53:05

C语言

2010-02-04 13:39:44

C++数组参数

2010-12-21 14:21:36

线程C#

2021-02-06 07:26:06

C++编程可变模板参数

2024-06-05 16:22:11

2010-07-12 21:44:51

HART协议

2010-09-08 11:59:38

蓝牙协议栈
点赞
收藏

51CTO技术栈公众号