C語(yǔ)言可變參數(shù)的原理和應(yīng)用
本文轉(zhuǎn)載自微信公眾號(hào)「編程學(xué)習(xí)基地 」,作者deroy 。轉(zhuǎn)載本文請(qǐng)聯(lián)系編程學(xué)習(xí)基地 公眾號(hào)。
概述
C語(yǔ)言中沒(méi)有函數(shù)重載,解決不定數(shù)目函數(shù)參數(shù)問(wèn)題變得比較麻煩;
即使采用C++,如果參數(shù)個(gè)數(shù)不能確定,也很難采用函數(shù)重載.對(duì)這種情況,有些人采用指針參數(shù)來(lái)解決問(wèn)題
var_list可變參數(shù)介紹
VA_LIST 是在C語(yǔ)言中解決變參問(wèn)題的一組宏,原型:
- typedef char* va_list;
其實(shí)就是個(gè)char*類型變量
除了var_list ,我們還需要幾個(gè)宏來(lái)實(shí)現(xiàn)可變參數(shù)
「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) )//第一個(gè)可選參數(shù)地址
- #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )//下一個(gè)參數(shù)地址
- #define va_end(ap) ( ap = (va_list)0 ) // 將指針置為無(wú)效
簡(jiǎn)單使用可變參數(shù)
- #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;
- }
啊這..
可變參數(shù)原理
在進(jìn)程中,堆棧地址是從高到低分配的.當(dāng)執(zhí)行一個(gè)函數(shù)的時(shí)候,將參數(shù)列表入棧,壓入堆棧的高地址部分,然后入棧函數(shù)的返回地址,接著入棧函數(shù)的執(zhí)行代碼,這個(gè)入棧過(guò)程,堆棧地址不斷遞減,
「黑客就是在堆棧中修改函數(shù)返回地址,執(zhí)行自己的代碼來(lái)達(dá)到執(zhí)行自己插入的代碼段的目的」.
函數(shù)在堆棧中的分布情況是:地址從高到低,依次是:函數(shù)參數(shù)列表,函數(shù)返回地址,函數(shù)執(zhí)行代碼段.
說(shuō)這么多直接上代碼演示吧..
- #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); //打印參數(shù)i在堆棧中的地址
- printf("arg_ptr = %p\n", arg_ptr); //打印va_start之后arg_ptr地址,比參數(shù)i的地址高sizeof(int)個(gè)字節(jié)
- /* 這時(shí)arg_ptr指向下一個(gè)參數(shù)的地址 */
- next = *((int*)arg_ptr);
- ReturnValue += next;
- next = va_arg(arg_ptr, int);
- printf("arg_ptr = %p\n", arg_ptr); //打印va_arg后arg_ptr的地址,比調(diào)用va_arg前高sizeof(int)個(gè)字節(jié)
- next = *((int*)arg_ptr);
- ReturnValue += next;
- /* 這時(shí)arg_ptr指向下一個(gè)參數(shù)的地址 */
- va_end(arg_ptr);
- return ReturnValue/argc;
- }
輸出:
- &argc = 0088FDD4
- arg_ptr = 0088FDD8
- arg_ptr = 0088FDDC
- AveInt(2, 2, 4): 3
「這個(gè)是為了介紹簡(jiǎn)單化,所以舉的例子」
這樣有點(diǎn)不大方便只能獲取兩個(gè)參數(shù)的,用可變參數(shù)改變一下
- #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
當(dāng)你理解之后你就會(huì)說(shuō)就這?這么簡(jiǎn)單,指定第一個(gè)參數(shù)是后面參數(shù)的總數(shù)就可以了,這還不隨隨便玩
別著急,精彩的來(lái)了,「可變參數(shù)的應(yīng)用」
可變參數(shù)應(yīng)用:實(shí)現(xiàn)log打印
- #include <stdarg.h>
- #include <stdio.h>
- #include <stdlib.h>
- /*定義一個(gè)回調(diào)函數(shù)指針*/
- typedef void (*libvlcFormattedLogCallback)(void* data, int level, const void* ctx, const char* message);
- enum libvlc_log_level {
- LIBVLC_DEBUG = 0, //調(diào)試
- LIBVLC_NOTICE = 2, //普通
- LIBVLC_WARNING = 3, //警告
- LIBVLC_ERROR = 4 } //錯(cuò)誤
- ;
- /*定義一個(gè)回調(diào)函數(shù)結(jié)構(gòu)體*/
- typedef struct CallbackData {
- void* managedData;
- libvlcFormattedLogCallback managedCallback;
- int minLogLevel; //log 級(jí)別
- } CallbackData;
- /*構(gòu)造回調(diào)函數(shù)結(jié)構(gòu)體*/
- 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;
- }
- /*回調(diào)函數(shù)*/
- 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信息并執(zhí)行回調(diào)函數(shù)*/
- 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)
- {
- /*注冊(cè)一個(gè)回調(diào)函數(shù)結(jié)構(gòu)體,level等級(jí)為L(zhǎng)IBVLC_WARNING 只要發(fā)送的log等級(jí)大于等于LIBVLC_WARNING次啊會(huì)觸發(fā)回調(diào)函數(shù)*/
- void* callbackData = makeCallbackData(formattedLogCallback, "context", LIBVLC_WARNING);
- /*發(fā)送四個(gè)等級(jí)的消息*/
- 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
這個(gè)使用示例精妙之處在于注冊(cè)一個(gè)指定level的回調(diào)函數(shù)makeCallbackData(formattedLogCallback, "context", LIBVLC_WARNING);
然后在發(fā)送log的時(shí)候根據(jù)level判斷是否執(zhí)行回調(diào)函數(shù),順便格式化log信息