为什么这种C语言中的函数重载方法有效?
我已经查看过用C语言做的一些方法,但我只找到了C99。
但是我遇到了以下解决方案,取自Lock Less 。
问题是,我不太明白它是如何工作的,并且想知道那里发生的事情的基本原理,以便能够更清楚地理解它。
我已经上网了一段时间,发现这个关于__VA_ARGS__ ,但不幸的是,仅此一点还不够。
我真的很感激有关此问题的解释或指导,任何forms的参考都会有所帮助。
我用GCC-5.4.1和-ansi标志编译了这段代码。
#include #include #include #define COUNT_PARMS2(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _, ...) _ #define COUNT_PARMS(...)\ COUNT_PARMS2(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) void count_overload1(int p1) { printf("One param: %d\n", p1); } void count_overload2(double *p1, const char *p2) { printf("Two params: %p (%f) %s\n", p1, *p1, p2); } void count_overload3(int p1, int p2, int p3) { printf("Three params: %c %d %d\n", p1, p2, p3); } void count_overload_aux(int count, ...) { va_list v; va_start(v, count); switch(count) { case 1: { int p1 = va_arg(v, int); count_overload1(p1); break; } case 2: { double *p1 = va_arg(v, double *); const char *p2 = va_arg(v, const char *); count_overload2(p1, p2); break; } case 3: { int p1 = va_arg(v, int); int p2 = va_arg(v, int); int p3 = va_arg(v, int); count_overload3(p1, p2, p3); break; } default: { va_end(v); printf("Invalid arguments to function 'count_overload()'"); exit(1); } } va_end(v); } #define count_overload(...)\ count_overload_aux(COUNT_PARMS(__VA_ARGS__), __VA_ARGS__) int main(int argc, char const *argv[]) { double d = 3.14; count_overload(1); count_overload(&d, "test"); count_overload('a',2,3); return 0; }
输出是:
One param: 1 Two params: 0x7ffc0fbcdd30 (3.140000) test Three params: a 2 3
我们分解COUNT_PARMS
和COUNT_PARMS2
宏。 第一个COUNT_PARMS
:
#define COUNT_PARMS(...)\ COUNT_PARMS2(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
由于宏不包含任何命名参数,因此传递给它的任何参数都将替换为__VA_ARGS__
。
以下是来电:
COUNT_PARMS(arg1) COUNT_PARMS(arg1, arg2) COUNT_PARMS(arg1, arg2, ,arg3)
将扩展为:
COUNT_PARMS2(arg1, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) COUNT_PARMS2(arg1, arg2, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) COUNT_PARMS2(arg1, arg2, arg3, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) // x
我将参数间隔开,以便您可以看到哪些参数彼此对应。 特别注意标记为x
的列。 这是传递给COUNT_PARMS
的参数数量,它是每种情况下的第11个参数。
现在让我们看看COUNT_PARMS2
:
#define COUNT_PARMS2(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _, ...) _
有11个名称参数,加上...
来说明任何其他参数。 宏的整个主体是_
,这是第11个参数的名称。 所以这个宏的目的是获取11个或更多的参数,并用第11个参数替换它们。
再看一下COUNT_PARAMS
的定义,它以这样的方式扩展,它调用COUNT_PARMS2
,第11个参数是传递给COUNT_PARAMS
的参数COUNT_PARAMS
。 这就是神奇的发生。
现在看一下main
中的函数调用:
count_overload(1); count_overload(&d, "test"); count_overload('a',2,3);
这些扩展到:
count_overload_aux(COUNT_PARMS(1), 1); count_overload_aux(COUNT_PARMS(&d, "test"), &d, "test"); count_overload_aux(COUNT_PARMS('a',2,3), 'a',2,3);
然后这个:
count_overload_aux(COUNT_PARMS2(1, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1), 1); count_overload_aux(COUNT_PARMS2(&d, "test", 10, 9, 8, 7, 6, 5, 4, 3, 2, 1), &d, "test"); count_overload_aux(COUNT_PARMS2('a',2,3, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1), 'a',2,3);
然后这个:
count_overload_aux(1, 1); count_overload_aux(2, &d, "test"); count_overload_aux(3, 'a',2,3);
最终结果是你可以调用一个带有可变数量参数的函数,而不必明确说明有多少参数。
dbush的好答案解释了宏正在做什么。 我想扩展这个并讨论省略号...
这里使用的。 你说关于可变参数宏和__VA_ARGS__
并没有帮助,所以我认为你也可能不太了解C省略号。
在C中,声明一个采用可变数量参数的函数的方法是使用省略号...
这种函数的一个主要例子是printf
,它至少可以使用一个参数,但它可以接受更多参数。
printf
的原型是:
int printf(const char *format, ...);
...
用于声明省略号。 请注意, ...
只能出现在命名参数的末尾,它不应该是寄存器变量,函数或数组类型,因此:
void foo(...) { }
无效,编译器会显示如下错误:
cc:6:10: error: ISO C requires a named argument before '...' void foo(...) ^~~
那么,怎么用呢? 您使用stdarg.h
定义的va_list
#include #include int sum(int num_of_values, ...) { va_list ap; // use the last named argument va_start(ap, num_of_values); int s = 0; for(int i = 0; i < num_of_values; ++i) { int v = va_arg(ap, int); s += v; } va_end(ap); return s; } int main(void) { printf("The sum is: %d\n", sum(5, 1, 2, 3, 4, 5)); }
将输出The sum is: 15
。
因此,当您的函数具有省略号时,必须首先声明类型为va_list
的变量,并将该变量作为第一个参数调用va_start
,将最后一个命名参数作为第二个参数调用。
然后你可以使用va_arg(ap,
来获取值,其中
是值的类型,在上面的例子中,它将是int
。 像printf
这样的printf
解析格式并使用转换说明符来获取正确的类型。 当printf
找到%d
,它会执行va_arg(ap, int)
,如果找到%f
它会执行va_arg(ap, float)
,如果找到%s
,它会执行va_arg(ap, char*)
等等上。 这就是为什么当格式和参数不匹配时printf
具有未定义的行为,因为在va_arg
调用中将使用错误的类型,这与后续调用va_arg
。 最后必须调用va_end
。
对于我在大学期间必须编写的微内核,我必须实现这些va_*
-macros。 我使用了编译器的行为,它将所有参数放在堆栈框架中,因此我的va_start
计算了最后一个命名参数之后的下一个值的堆栈中的地址。 va_arg
基于va_start
的计算加上由类型确定的偏移量移动堆栈,同时还使用上次使用的参数更新ap
变量。 让它工作很棘手,但最后它在该系统上工作,但x86_64
上的相同实现只产生垃圾。
例如在GCC编译器中如何实现这一点,我不知道,但我怀疑GCC做了类似的事情。 我已经检查了源代码gcc/builtins.c
:4855但是像往常一样,我发现GCC代码非常复杂。