我已经查看过用C语言做的一些方法,但我只找到了C99。
但是我从Lock Less获得了以下解决方案。
问题是,我不太明白它是如何运作的,并且想知道那里发生的事情的基本原理,以便能够更清楚地理解它。
我已经上网了一段时间,发现this关于 __ VA_ARGS __ ,但遗憾的是,仅凭这一点还不够。
我真的很感激有关此问题的解释或指导,任何类型的参考都会有所帮助。
我使用 -ansi 标记用 GCC-5.4.1 编译了这段代码。
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#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
答案 0 :(得分:5)
让我们分解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
的参数数量。这就是神奇的发生。
现在查看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);
最终结果是你可以调用一个带有可变数量参数的函数,而不必明确说明有多少参数。
答案 1 :(得分:2)
dbush's great answer解释了宏正在做什么。我想
展开这个并讨论这里使用的省略号...
。你说
关于可变参数宏和__VA_ARGS__
的阅读并没有帮助,所以我
假设您可能也不太了解C省略号。
在C语言中,声明一个带有可变数量参数的函数是
使用省略号...
。这种函数的一个主要例子是printf
可以至少使用一个参数,但它可以接受更多参数。
printf
的原型是:
int printf(const char *format, ...);
...
用于声明省略号。请注意,...
只能
出现在命名参数的末尾,它不应该是一个寄存器
变量,函数或数组类型,因此:
void foo(...)
{
}
无效,编译器会显示如下错误:
c.c:6:10: error: ISO C requires a named argument before ‘...’
void foo(...)
^~~
那么,怎么用呢?您使用stdarg.h
va_list
#include<stdio.h>
#include<stdarg.h>
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, <type>)
获取值<type>
值的类型,如上例所示,它将是int
。功能如
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_*
。
对于我在大学期间必须写的微内核,我有
实现这些va_start
- 宏。我用它编译器的行为
将所有参数放在堆栈框架中,以便我的va_arg
计算出地址
最后一个命名参数后面的下一个值的堆栈。 va_start
移动了堆栈
基于ap
的计算加上由类型确定的偏移量
还使用上次使用的参数更新x86_64
变量。它是
让它发挥作用是棘手的,但最后它在该系统上工作,但是相同的实现
在public IActionResult orderBy([FromQuery] string fields)
{
string[] _fields = fields.Split(',');
try {
PropertyInfo[] Props = _fields.Select(field => typeof(Product).GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) ).ToArray();
var query = _context.Product
.AsNoTracking()
.OrderBy(p => Props.First().GetValue(p));
for (int i = 1; i < Props.Count(); i++)
query = query.ThenBy(p => Props[i].GetValue(p));
return Ok( query.ToList() );
}
catch (Exception ex) {
return BadRequest(new { Title = ex.GetType().Name, Error = ex });
}
}
上只产生垃圾。
例如,如何实现这一点
在GCC编译器中,我不知道,但我怀疑GCC做了些什么
类似。我检查了源代码gcc/builtins.c
:4855,但像往常一样,
我发现GCC代码非常复杂。