C语言可变参数宏

标签:
杂谈 |
分类: 嵌入式软件 |
实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏_ _VA_ARGS_ _就可以被用在替换部分中,替换省略号所代表的字符串。比如:
#define PR(...) printf(__VA_ARGS__)
gcc有一种扩展语法,如果##运算符用在__VA_ARGS__前面,除了起连接作用之外还有特殊的含义,例如内核代码net/netfilter/nf_conntrack_proto_sctp.c中的:
#define DEBUGP(format, ...) printk(format, ## __VA_ARGS__)
printk这个内核函数相当于printf,也带有格式化字符串和可变参数,由于内核不能调用libc的函数,所以另外实现了一个打印函数。这个 函数式宏定义可以这样调用:DEBUGP("info no. %d", 1)。也可以这样调用:DEBUGP("info")。后者相当于可变参数部分传了一个空参数,但展开后并不是printk("info",),而是 printk("info"),当__VA_ARGS是空参数时,##运算符把它前面的,号“吃”掉了。
from:https://www.cnblogs.com/Anker/p/3418792.html
“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。
在普通的宏定义中,预处理器一般把空格解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些##来替代空格。
1 #define TYPE1(type,name) type name_##type##_type
2 #define TYPE2(type,name) type name##_##type##_type
TYPE1(int,
c);
TYPE2(int,
d);转换为: int
from:https://www.cnblogs.com/lanxuezaipiao/p/3535626.html
如果你是一名C程序员,你肯定很熟悉宏,它们非常强大,如果正确使用可以让你的工作事半功倍。然而,如果你在定义宏时很随意没有认真检查,那么它们可能使你发狂,浪费N多时间。在很多的C程序中,你可能会看到许多看起来不是那么直接的较特殊的宏定义。下面就是一个例子:
1
2
|
#define __set_task_state(tsk,
state_value) do {
(tsk)->state = (state_value);
} while (0) |
在Linux内核和其它一些著名的C库中有许多使用do{...}while(0)的宏定义。这种宏的用途是什么?有什么好处?
Google的Robert
Love(先前从事Linux内核开发)给我们解答如下:
do{...}while(0)在C中是唯一的构造程序,让你定义的宏总是以相同的方式工作,这样不管怎么使用宏(尤其在没有用大括号包围调用宏的语句),宏后面的分号也是相同的效果。
这句话听起来可能有些拗口,其实用一句话概括就是:使用do{...}while(0)构造后的宏定义不会受到大括号、分号等的影响,总是会按你期望的方式调用运行。
例如:
1
|
#define foo(x) bar(x); baz(x) |
然后你可能这样调用:
1
|
foo(wolf); |
这将被宏扩展为:
1
|
bar(wolf); baz(wolf); |
这的确是我们期望的正确输出。下面看看如果我们这样调用:
1
2
|
if (!feral) foo(wolf); |
那么扩展后可能就不是你所期望的结果。上面语句将扩展为:
1
2
3
|
if (!feral) bar(wolf); baz(wolf); |
显而易见,这是错误的,也是大家经常易犯的错误之一。
几乎在所有的情况下,期望写多语句宏来达到正确的结果是不可能的。你不能让宏像函数一样行为——在没有do/while(0)的情况下。
如果我们使用do{...}while(0)来重新定义宏,即:
1
|
#define foo(x) do { bar(x); baz(x); } while
(0) |
现在,该语句功能上等价于前者,do能确保大括号里的逻辑能被执行,而while(0)能确保该逻辑只被执行一次,即与没有循环时一样。
对于上面的if语句,将会被扩展为:
1
2
|
if (!feral) do {
bar(wolf); baz(wolf); } while (0); |
从语义上讲,它与下面的语句是等价的:
1
2
3
4
|
if (!feral) { bar(wolf); baz(wolf); } |
这里你可能感到迷惑不解了,为什么不用大括号
直接
把宏包围起来呢?为什么非得使用do/while(0)逻辑呢?
例如,我们用大括号来定义宏如下:
1
|
#define foo(x) |
这对于上面举的if语句的确能被正确扩展,但是如果我们有下面的语句调用呢:
1
2
3
4
|
if (!feral) foo(wolf); else bin(wolf); |
宏扩展后将变成:
1
2
3
4
5
6
|
if (!feral) { bar(wolf); baz(wolf); }; else bin(wolf); |
大家可以看出,这就有语法错误了。
总结:Linux和其它代码库里的宏都用do/while(0)来包围执行逻辑,因为它能确保宏的行为总是相同的,而不管在调用代码中使用了多少分号和大括号。
from:https://www.cnblogs.com/chaunceyctx/p/7372794.html
int f(...) { ...... ...... ...... }
不能这样定义,因为这是ANSI C 所要求的,至少得定义一个固定参数。这个参数将被传递给va_start(),然后用va_arg()和va_end()来确定所有实际调用时可变长参数的类型和值
from:https://www.cnblogs.com/pianist/p/3315801.html
int vsprintf(char *buf, const char *fmt, va_list args)
{
char* p;
char tmp[256];
va_list p_next_arg = args;
for (p=buf;*fmt;fmt++) {
if (*fmt != '%') {
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt) {
case 'x':
itoa(tmp, *((int*)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case 's':
break;
default:
break;
}
}
return (p - buf);
}