您好,登錄后才能下訂單哦!
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);
看完上述內容,你們掌握C語言中可變參數的原理是什么的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。