C言語での変数パラメータの原理と応用

出典:WeChat公式アカウント「プログラミング学習ベース」

2021年の2番目の記事、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

これは導入を簡略化するためであるため、例

これは少し不便です。取得できるパラメータは2つだけです。変数パラメータを使用して変更してください。

#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

あなたが理解するとき、あなたはこれを言うでしょう?それはそれと同じくらい簡単です、最初のパラメータが次のパラメータの総数であることを指定するだけです、これはカジュアルゲームではありません

心配しないでください、素晴らしいことがやってくる、可変パラメータの適用

可変パラメータアプリケーション:ログ印刷を実現

#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);

次に、送信時にloglevelコールバック関数を実行するかどうかの判断に従ってlog方法で情報をフォーマットします

おすすめ

転載: blog.csdn.net/qq_44519484/article/details/112211361