在C函数声明中,最后一个参数为"..."时是什么意思?

67

我经常看到像这样声明函数的方式:

void Feeder(char *buff, ...)

"..."是什么意思?

7个回答

51

23

变长参数函数

变长参数函数可以接受可变数量的参数,并且在最后一个参数的位置上使用省略号来声明。类似 printf 函数就是一个例子。

典型的声明形式为:

    int check(int a, double b, ...);

变长参数函数必须至少有一个命名参数,例如,

    char *wrong(...);  

在C语言中不允许使用该语法。


7

三个点“...”被称为省略号。在函数中使用它们可以使该函数成为一个可变参数函数。在函数声明中使用它们意味着该函数将接受在已定义参数之后的任意数量的参数。

例如:

Feeder("abc");
Feeder("abc", "def");

这些都是有效的函数调用,但以下内容则不是:

Feeder();

7

变参函数(多参数函数)

维基百科

#include <stdarg.h>

double average(int count, ...)
{
    va_list ap;
    int j;
    double tot = 0;
    va_start(ap, count); //Requires the last fixed parameter (to get the address)
    for(j=0; j<count; j++)
        tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.
    va_end(ap);
    return tot/count;
}

6

...作为最后一个参数的函数称为变参函数(Cppreference. 2016). 这个...用于允许带有未指定类型的可变长度参数。

当我们不确定参数数量或类型时,可以使用变参函数。

变参函数示例: 假设我们需要一个求和函数来返回可变数量参数的总和。我们可以在这里使用变参函数。

#include <stdio.h>
#include <stdarg.h>

int sum(int count, ...)
{
    int total, i, temp;
    total = 0;
    va_list args;
    va_start(args, count);
    for(i=0; i<count; i++)
    {
        temp = va_arg(args, int);
        total += temp;
    }
    va_end(args);
    return total;
}

int main()
{
    int numbers[3] = {5, 10, 15};
    // Get summation of all variables of the array
    int sum_of_numbers = sum(3, numbers[0], numbers[1], numbers[2]);
    printf("Sum of the array %d\n", sum_of_numbers);
    // Get summation of last two numbers of the array
    int partial_sum_of_numbers = sum(2, numbers[1], numbers[2]);
    printf("Sum of the last two numbers of the array %d\n", partial_sum_of_numbers);
    return 0;
}

输出:

C output

练习问题: 一个简单的可变参数函数练习问题可以在hackerrank练习问题中找到

参考资料:


6
这意味着正在声明一个可变参数函数。

1
  • ... = 三个点 = 省略号
  • 意思是: 可变数量的参数
  • 与普通函数相比: 参数数量固定
  • 带有...参数的函数被称为: 可变参数函数

可变参数函数

定义

  • 有效:
    • int validFunctionWithNamedParameterThenEllipsis(int a, double b, ...);
  • 无效:
    • int invalidFunctionOnlyEllipsis(...);

示例

常见情况:

int printf(const char *format, ...)

调用:

printf("year=%d, name=%s", 2021, "crifan");

->

  • format == "year=%d, name=%s"
    • 命名参数
  • ... == 2021, "crifan"
    • 可变数量的参数
      • 这里总共2个参数
        • 第一个:整数类型2021
        • 第二个:字符串类型"crifan"

如何获取/计算可变参数的值

  • 核心逻辑:使用va_list,并配合va_startva_argva_end函数

相关定义

#include <stdarg.h>

void va_start(va_list ap, last_arg);
type va_arg(va_list ap, type);
void va_end(va list ap);

示例

平均值

#include <stdarg.h>
#include <stdio.h>

double average(int count, ...) {
    va_list ap;
    int j;
    double sum = 0;

    va_start(ap, count); /* Requires the last fixed parameter (to get the address) */
    for (j = 0; j < count; j++) {
        sum += va_arg(ap, int); /* Increments ap to the next argument. */
    }
    va_end(ap);

    return sum / count;
}

int main(int argc, char const *argv[]) {
    printf("%f\n", average(3, 1, 2, 3));
    return 0;
}

maxof

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>

int maxof(int, ...) ;
void f(void);

main(){
        f();
        exit(EXIT_SUCCESS);
}

int maxof(int n_args, ...){
        register int i;
        int max, a;
        va_list ap;

        va_start(ap, n_args);
        max = va_arg(ap, int);
        for(i = 2; i <= n_args; i++) {
                if((a = va_arg(ap, int)) > max)
                        max = a;
        }

        va_end(ap);
        return max;
}

void f(void) {
        int i = 5;
        int j[256];
        j[42] = 24;
        printf("%d\n",maxof(3, i, j[42], 0));
}

execl

#include <stdarg.h>

#define  MAXARGS     31

/*
 * execl is called by
 * execl(file, arg1, arg2, ..., (char *)(0));
 */
int execl(const char *file, const char *args, ...)
{
    va_list ap;
    char *array[MAXARGS +1];
    int argno = 0;

    va_start(ap, args);
    while (args != 0 && argno < MAXARGS)
    {
        array[argno++] = args;
        args = va_arg(ap, const char *);
    }
    array[argno] = (char *) 0;
    va_end(ap);
    return execv(file, array);
}

我的案例:钩取系统调用()

/*==============================================================================
 Hook: syscall()
==============================================================================*/

int syscall(int, ...);

// normally max number of syscall parameter is not exceed 8
// refer: https://opensource.apple.com/source/xnu/xnu-4570.1.46/bsd/kern/syscalls.master
int MaxSupportArgNum_syscall = 16;

%hookf(int, syscall, int number, ...){
    os_log(OS_LOG_DEFAULT, "hook_syscall_stat_stat64: number=%d", number);

    // Setting up some variables to get all the parameters from syscall
    void *paraPtr, *paraList[MaxSupportArgNum_syscall];
//    char *paraPtr, *paraList[MaxSupportArgNum_syscall];
    va_list argList;
    int curParaNum = 0;

    va_start(argList, number);
    while ((paraPtr = (void *) va_arg(argList, void *))) {
    //    while ((paraPtr = (char *) va_arg(argList, char *))) {
        paraList[curParaNum] = paraPtr;
        curParaNum += 1;
        os_log(OS_LOG_DEFAULT, "hook_syscall_stat_stat64: [%d] paraPtr=%p", curParaNum, paraPtr);
    }
    va_end(argList);

//    os_log(OS_LOG_DEFAULT, "hook_syscall_stat_stat64: argList=%{public}s", argList);
    os_log(OS_LOG_DEFAULT, "hook_syscall_stat_stat64: curParaNum=%d", curParaNum);

    bool isStat = (SYS_stat == number);
    bool isStat64 = (SYS_stat64 == number);
    if (isStat || isStat64){
        char* curPath = (char *)paraList[0];
        os_log(OS_LOG_DEFAULT, "hook_syscall_stat_stat64: isStat=%{bool}d, isStat64=%{BOOL}d, curPath=%{public}s", isStat, isStat64, curPath);
        
        bool isJbPath = isJailbreakPath(curPath);
        if (isJbPath){
            os_log(OS_LOG_DEFAULT, "hook_syscall_stat_stat64: IS jailbreak path: %{public}s", curPath);
            return OPEN_FAILED;
        } else {
            os_log(OS_LOG_DEFAULT, "hook_syscall_stat_stat64: NOT jailbreak path: %{public}s", curPath);
        }
    }

//    return %orig;
//    return %orig(number, ...);
//    int retValue = %orig();

//    int retValue = callOriginSyscall(number, curParaNum, paraList);
////    int retValue = callOriginSyscall(number, curParaNum, (void *)paraList);
//    os_log(OS_LOG_DEFAULT, "hook_syscall_stat_file: retValue=%d", retValue);
//    return retValue;

    int paraNum = curParaNum;

    int syscallRetValue = -1;

    if (0 == paraNum){
        syscallRetValue = %orig(number);
    } else if (1 == paraNum){
        void* para1 = paraList[0];
        os_log(OS_LOG_DEFAULT, "hook_syscall_orig: para1=%p", para1);
        syscallRetValue = %orig(number, para1);
    } else if (2 == paraNum){
        void* para1 = paraList[0];
        void* para2 = paraList[1];
        os_log(OS_LOG_DEFAULT, "hook_syscall_orig: para1=%p,para2=%p", para1, para2);
        syscallRetValue = %orig(number, para1, para2);
    } else if (3 == paraNum){
        void* para1 = paraList[0];
        void* para2 = paraList[1];
        void* para3 = paraList[2];
        os_log(OS_LOG_DEFAULT, "hook_syscall_orig: para1=%p,para2=%p,para3=%p", para1, para2, para3);
        syscallRetValue = %orig(number, para1, para2, para3);
    } else if (4 == paraNum){
        void* para1 = paraList[0];
        void* para2 = paraList[1];
        void* para3 = paraList[2];
        void* para4 = paraList[3];
        os_log(OS_LOG_DEFAULT, "hook_syscall_orig: para1=%p,para2=%p,para3=%p,para4=%p", para1, para2, para3, para4);
        syscallRetValue = %orig(number, para1, para2, para3, para4);
    } else if (5 == paraNum){
        void* para1 = paraList[0];
        void* para2 = paraList[1];
        void* para3 = paraList[2];
        void* para4 = paraList[3];
        void* para5 = paraList[4];
        os_log(OS_LOG_DEFAULT, "hook_syscall_orig: para1=%p,para2=%p,para3=%p,para4=%p,para5=%p", para1, para2, para3, para4, para5);
        syscallRetValue = %orig(number, para1, para2, para3, para4, para5);
    } else if (6 == paraNum){
        void* para1 = paraList[0];
        void* para2 = paraList[1];
        void* para3 = paraList[2];
        void* para4 = paraList[3];
        void* para5 = paraList[4];
        void* para6 = paraList[5];
        os_log(OS_LOG_DEFAULT, "hook_syscall_orig: para1=%p,para2=%p,para3=%p,para4=%p,para5=%p,para6=%p", para1, para2, para3, para4, para5, para6);
        syscallRetValue = %orig(number, para1, para2, para3, para4, para5, para6);
    } else if (7 == paraNum){
        void* para1 = paraList[0];
        void* para2 = paraList[1];
        void* para3 = paraList[2];
        void* para4 = paraList[3];
        void* para5 = paraList[4];
        void* para6 = paraList[5];
        void* para7 = paraList[6];
        os_log(OS_LOG_DEFAULT, "hook_syscall_orig: para1=%p,para2=%p,para3=%p,para4=%p,para5=%p,para6=%p,para7=%p", para1, para2, para3, para4, para5, para6, para7);
        syscallRetValue = %orig(number, para1, para2, para3, para4, para5, para6, para7);
    } else if (8 == paraNum){
        void* para1 = paraList[0];
        void* para2 = paraList[1];
        void* para3 = paraList[2];
        void* para4 = paraList[3];
        void* para5 = paraList[4];
        void* para6 = paraList[5];
        void* para7 = paraList[6];
        void* para8 = paraList[7];
        os_log(OS_LOG_DEFAULT, "hook_syscall_orig: para1=%p,para2=%p,para3=%p,para4=%p,para5=%p,para6=%p,para7=%p,para8=%p", para1, para2, para3, para4, para5, para6, para7, para8);
        syscallRetValue = %orig(number, para1, para2, para3, para4, para5, para6, para7, para8);
    } else if (9 == paraNum){
        void* para1 = paraList[0];
        void* para2 = paraList[1];
        void* para3 = paraList[2];
        void* para4 = paraList[3];
        void* para5 = paraList[4];
        void* para6 = paraList[5];
        void* para7 = paraList[6];
        void* para8 = paraList[7];
        void* para9 = paraList[8];
        os_log(OS_LOG_DEFAULT, "hook_syscall_orig: para1=%p,para2=%p,para3=%p,para4=%p,para5=%p,para6=%p,para7=%p,para8=%p,para9=%p", para1, para2, para3, para4, para5, para6, para7, para8, para9);
        syscallRetValue = %orig(number, para1, para2, para3, para4, para5, para6, para7, para8, para9);
    }

    os_log(OS_LOG_DEFAULT, "hook_syscall_orig: syscallRetValue=%d", syscallRetValue);
    return syscallRetValue;
}

一些注记

va_start

调用va_start时,last_arg是最后一个命名参数,而不是第一个

例如:int open(const char *path, int oflag, ...);

  • 正确的使用方法
    • va_start(argList, oflag);
  • 错误的使用方法
    • va_start(argList, path);
      • (XCode的clang)编译器会发出警告
        • 'va_start'的第二个参数不是最后一个命名参数

va_arg

type va_arg(va_list ap, type);

当传递type时,有一个特殊情况:

传递以下类型之一:

  • char
  • unsigned char
  • unsigned short

但返回值总是:

  • unsigned int

-> 因此,当代码如下所示:

curPara = (mode_t) va_arg(argList, mode_t);

按照以下方式进行转换:

#include <sys/stat.h>
#include <sys/types.h>

-> mode_t等价于unsigned short

curPara = (mode_t) va_arg(argList, unsigned short);

因此编译器警告:

“va_arg”的第二个参数是可以晋升的类型“mode_t”(又名“unsigned short”);这个va_arg具有未定义的行为,因为参数将会晋升到“int”。

将其改为:

curPara = (mode_t) va_arg(argList, unsigned int);

可以避免警告。

相关文档


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接