加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

C语言可变参数宏

(2018-10-10 08:54:38)
标签:

杂谈

分类: 嵌入式软件

from:https://www.cnblogs.com/langzou/p/6674528.html

#define LOG(format, ...) fprintf(stdout, format, __VA_ARGS__)
其中,...表示可变参数列表,__VA_ARGS__在预处理中,会被实际的参数集(实参列表)所替换。

from:http://blog.chinaunix.net/uid-20620288-id-4114964.html
3.可变参数宏 ...和_ _VA_ARGS_ _
实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏_ _VA_ARGS_ _就可以被用在替换部分中,替换省略号所代表的字符串。比如:
#define PR(...) printf(__VA_ARGS__)
宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉的作用,否则会编译出错, 你可以试试

from:http://blog.chinaunix.net/uid-20620288-id-4114964.html

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); 转换为:int  name_int_type ; (因为##号将后面分为 name_ 、type 、 _type三组,替换后强制连接)
TYPE2(int, d);转换为: int  d_int_type ; (因为##号将后面分为 name、_、type 、_type四组,替换后强制连接)


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)  { bar(x); baz(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

 这说明它是一个字符指针。 
    其中的: (char*)(&fmt) + 4) 表示的是...中的第一个参数。 
    如果不懂,我再慢慢的解释: 

    C语言中,参数压栈的方向是从右往左。 

    也就是说,当调用printf函数的适合,先是最右边的参数入栈。 
    fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素。 
    fmt也是个变量,它的位置,是在栈上分配的,它也有地址。 
    对于一个char *类型的变量,它入栈的是指针,而不是这个char *型变量。 
    换句话说: 
    你sizeof(p) (p是一个指针,假设p=&i,i为任何类型的变量都可以) 
    得到的都是一个固定的值。(我的计算机中都是得到的4) 
    当然,我还要补充的一点是:栈是从高地址向低地址方向增长的。 
    ok! 
    现在我想你该明白了:为什么说(char*)(&fmt) + 4) 表示的是...中的第一个参数的地址



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

       
    我们还是先不看看它的具体内容。 
    想想printf要左什么吧 
    它接受一个格式化的命令,并把指定的匹配的参数格式化输出。 
    
    ok,看看i = vsprintf(buf, fmt, arg); 
     vsprintf返回的是一个长度,我想你已经猜到了:是的,返回的是要打印出来的字符串的长度 
    其实看看printf中后面的一句:write(buf, i);你也该猜出来了。 
    write,顾名思义:写操作,把buf中的i个元素的值写到终端。 
    
    所以说:vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。 
    我代码中的vsprintf只实现了对16进制的格式化。 
    
    你只要明白vsprintf的功能是什么,就会很容易弄懂上面的代码。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有