1. 程式人生 > 其它 >C語言可變引數的原理和應用

C語言可變引數的原理和應用

技術標籤:C



來源:微信公眾號「程式設計學習基地」

2021年的第二篇文章,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資訊