C可变参数宏不编译
C variadic macro do not compile
我写的这段代码有一些问题。 GCC 不喜欢它:
#define _DEBUG_ADD(string, ...) \
do{ \
if (EVALUATE_TYPE(string)){ \
size_t size = strlen(string) + BUFFER_SIZE_DEBUG; \
char *buffer = alloca(size); \
bzero(buffer, size); \
snprintf(buffer, size, string, __VA_ARGS__); \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)
但是 gcc 显示这个错误:
../debug.h:33:42: error: expected expression before ')' token
snprintf(buffer, size, string, __VA_ARGS__);
我阅读了关于 variadic macro 的 gcc 文档,我没有做错。
谁能指出我的错误?我完全迷路了。
编辑:
我就是这么用的
_DEBUG_ADD("Bbox found @ %f %f %f %f", box[0], box[1], box[2], box[3]);
如果我使用您显示的代码片段并填充缺失的位以获得完整的可编译源文件,我不会收到任何错误:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define _DEBUG_ADD(string, ...) \
do{ \
if (EVALUATE_TYPE(string)){ \
size_t size = strlen(string) + BUFFER_SIZE_DEBUG; \
char *buffer = alloca(size); \
bzero(buffer, size); \
snprintf(buffer, size, string, __VA_ARGS__); \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)
#define EVALUATE_TYPE(s) 1
#define BUFFER_SIZE_DEBUG 128
#define DEBUG_STREAM stderr
void test(double box[4])
{
_DEBUG_ADD("Bbox found @ %f %f %f %f", box[0], box[1], box[2], box[3]);
}
-->
$ gcc -fsyntax-only -Wall test.c
$
这就是为什么我们对minimal, complete, verifiable examples如此大惊小怪的原因。我们不想浪费很多时间找错树。
但是,在这种情况下,我强烈怀疑您的问题实际上是由 this:
这样的代码触发的
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define _DEBUG_ADD(string, ...) \
do{ \
if (EVALUATE_TYPE(string)){ \
size_t size = strlen(string) + BUFFER_SIZE_DEBUG; \
char *buffer = alloca(size); \
bzero(buffer, size); \
snprintf(buffer, size, string, __VA_ARGS__); \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)
#define EVALUATE_TYPE(s) 1
#define BUFFER_SIZE_DEBUG 128
#define DEBUG_STREAM stderr
void test(void)
{
_DEBUG_ADD("got here 1");
}
它产生的错误消息与您显示的几乎相同:
$ gcc -fsyntax-only -Wall test.c
test.c: In function ‘test’:
test.c:11:43: error: expected expression before ‘)’ token
snprintf(buffer, size, string, __VA_ARGS__); \
^
test.c:21:3: note: in expansion of macro ‘_DEBUG_ADD’
_DEBUG_ADD("got here 1");
^~~~~~~~~~
当您在格式字符串后不给 _DEBUG_ADD
任何参数时,__VA_ARGS__
将展开为空,因此 "compiler proper" 会看到
snprintf(buffer, size, string, );
这确实是一个语法错误。这就是 GNU comma-deletion extension 的用途:如果将 ##
放在 ,
和 __VA_ARGS__
之间,当 __VA_ARGS__
扩展为空时,逗号将被删除。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define _DEBUG_ADD(string, ...) \
do{ \
if (EVALUATE_TYPE(string)){ \
size_t size = strlen(string) + BUFFER_SIZE_DEBUG; \
char *buffer = alloca(size); \
bzero(buffer, size); \
snprintf(buffer, size, string, ##__VA_ARGS__); \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)
#define EVALUATE_TYPE(s) 1
#define BUFFER_SIZE_DEBUG 128
#define DEBUG_STREAM stderr
void test(void)
{
_DEBUG_ADD("got here 1");
}
-->
$ gcc -fsyntax-only -Wall test.c
$
遗憾的是,此扩展仅在 GCC 和 Clang 中可用。我知道 C 和 C++ 委员会正在讨论添加一个类似但不兼容的特性 Real Soon Now(参见 this question and its answers, and also C committee documents N2023 and N2153 上的评论),但即使他们这样做了,也可能需要十年左右的时间无处不在,足以使用。
顺便说一句,名称 _DEBUG_ADD
以下划线开头。所有以下划线开头的名称至少在某些上下文中保留供 C 编译器和库内部使用。在您对该语言有 很多 的更多经验之前,您不应该在代码中给任何东西起一个以下划线开头的名称。 (使用 名称以下划线开头的东西是可以的,例如 __VA_ARGS__
和 _IONBF
,但前提是它们被记录在案。)
我写的这段代码有一些问题。 GCC 不喜欢它:
#define _DEBUG_ADD(string, ...) \
do{ \
if (EVALUATE_TYPE(string)){ \
size_t size = strlen(string) + BUFFER_SIZE_DEBUG; \
char *buffer = alloca(size); \
bzero(buffer, size); \
snprintf(buffer, size, string, __VA_ARGS__); \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)
但是 gcc 显示这个错误:
../debug.h:33:42: error: expected expression before ')' token
snprintf(buffer, size, string, __VA_ARGS__);
我阅读了关于 variadic macro 的 gcc 文档,我没有做错。
谁能指出我的错误?我完全迷路了。
编辑:
我就是这么用的
_DEBUG_ADD("Bbox found @ %f %f %f %f", box[0], box[1], box[2], box[3]);
如果我使用您显示的代码片段并填充缺失的位以获得完整的可编译源文件,我不会收到任何错误:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define _DEBUG_ADD(string, ...) \
do{ \
if (EVALUATE_TYPE(string)){ \
size_t size = strlen(string) + BUFFER_SIZE_DEBUG; \
char *buffer = alloca(size); \
bzero(buffer, size); \
snprintf(buffer, size, string, __VA_ARGS__); \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)
#define EVALUATE_TYPE(s) 1
#define BUFFER_SIZE_DEBUG 128
#define DEBUG_STREAM stderr
void test(double box[4])
{
_DEBUG_ADD("Bbox found @ %f %f %f %f", box[0], box[1], box[2], box[3]);
}
-->
$ gcc -fsyntax-only -Wall test.c
$
这就是为什么我们对minimal, complete, verifiable examples如此大惊小怪的原因。我们不想浪费很多时间找错树。
但是,在这种情况下,我强烈怀疑您的问题实际上是由 this:
这样的代码触发的#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define _DEBUG_ADD(string, ...) \
do{ \
if (EVALUATE_TYPE(string)){ \
size_t size = strlen(string) + BUFFER_SIZE_DEBUG; \
char *buffer = alloca(size); \
bzero(buffer, size); \
snprintf(buffer, size, string, __VA_ARGS__); \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)
#define EVALUATE_TYPE(s) 1
#define BUFFER_SIZE_DEBUG 128
#define DEBUG_STREAM stderr
void test(void)
{
_DEBUG_ADD("got here 1");
}
它产生的错误消息与您显示的几乎相同:
$ gcc -fsyntax-only -Wall test.c
test.c: In function ‘test’:
test.c:11:43: error: expected expression before ‘)’ token
snprintf(buffer, size, string, __VA_ARGS__); \
^
test.c:21:3: note: in expansion of macro ‘_DEBUG_ADD’
_DEBUG_ADD("got here 1");
^~~~~~~~~~
当您在格式字符串后不给 _DEBUG_ADD
任何参数时,__VA_ARGS__
将展开为空,因此 "compiler proper" 会看到
snprintf(buffer, size, string, );
这确实是一个语法错误。这就是 GNU comma-deletion extension 的用途:如果将 ##
放在 ,
和 __VA_ARGS__
之间,当 __VA_ARGS__
扩展为空时,逗号将被删除。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define _DEBUG_ADD(string, ...) \
do{ \
if (EVALUATE_TYPE(string)){ \
size_t size = strlen(string) + BUFFER_SIZE_DEBUG; \
char *buffer = alloca(size); \
bzero(buffer, size); \
snprintf(buffer, size, string, ##__VA_ARGS__); \
fwrite(buffer, strlen(buffer), 1, DEBUG_STREAM); }} \
while(0)
#define EVALUATE_TYPE(s) 1
#define BUFFER_SIZE_DEBUG 128
#define DEBUG_STREAM stderr
void test(void)
{
_DEBUG_ADD("got here 1");
}
-->
$ gcc -fsyntax-only -Wall test.c
$
遗憾的是,此扩展仅在 GCC 和 Clang 中可用。我知道 C 和 C++ 委员会正在讨论添加一个类似但不兼容的特性 Real Soon Now(参见 this question and its answers, and also C committee documents N2023 and N2153 上的评论),但即使他们这样做了,也可能需要十年左右的时间无处不在,足以使用。
顺便说一句,名称 _DEBUG_ADD
以下划线开头。所有以下划线开头的名称至少在某些上下文中保留供 C 编译器和库内部使用。在您对该语言有 很多 的更多经验之前,您不应该在代码中给任何东西起一个以下划线开头的名称。 (使用 名称以下划线开头的东西是可以的,例如 __VA_ARGS__
和 _IONBF
,但前提是它们被记录在案。)