如何调试宏?

how to debug macro?

有这个代码:

#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

#define check(x) ({int _val = (x); (_val ==-1? \
    ({fprintf(stderr, "Error (" __FILE__ ":%d) -- " \
    "%s\n",__LINE__,strerror(errno)); \
    exit(-1);-1;}) : _val); })

int main (int argc, char **argv) {

    if (argc!=2){
        fprintf(stderr, "usage: %s <filename>\n", argv[0]);
        exit(-1);
    }

    int fd;
    fd=open(argv[1], O_RDONLY);

    if(fd<0){
        perror("open");
        fprintf(stderr,"errno = %s\n",strerror(errno));
        exit(-1);
    }

    for(char c;check(read(fd,&c,1))>0;) //macro "check(x)" HERE
        fputc(c,stdout);

    return 0;
}

它编译没有错误,但我可以看到宏没有输出错误消息(__FILE____LINE__ 也没有),因为它定义了宏。如何解决? (一般来说,如何调试宏,至少如何为它输出一些错误)?

$cc -pedantic a.c

a.c: In function ‘main’:
a.c:10:2: warning: ISO C forbids braced-groups within expressions [-Wpedantic]
  ({fprintf(stderr, "Error (" __FILE__ ":%d) -- " \
  ^
a.c:30:13: note: in expansion of macro ‘check’
  for(char c;check(read(fd,&c,1))>0;)
             ^~~~~
a.c:9:18: warning: ISO C forbids braced-groups within expressions [-Wpedantic]
 #define check(x) ({int _val = (x); (_val ==-1? \
                  ^
a.c:30:13: note: in expansion of macro ‘check’
  for(char c;check(read(fd,&c,1))>0;)

没有单独提到关于for loop(只是它的位置,因为宏在那里,但这不是错误)声明。只谈宏观。所以请关注宏问题而不是for循环。

编辑1: 宏实际上来自这个人: youtube tutorial(在视频末尾),他使用 clang,所以我不知道 clang 用作预处理器的是什么(因此对他有用)。

EDIT2(来自 a.i 的输出 - 预处理输出):

# 304 "/usr/include/fcntl.h" 3 4

# 8 "a.c" 2







# 14 "a.c"
int main (int argc, char **argv) {

我已经删除了所有噪音。但是你可以看到#include <fcntl.h>int main之间,什么都没有。但是应该有#define check(x)...。所以它没有告诉我为什么它不见了。

how to debug macro?

阅读有关 C 的更多信息,尤其是 Modern C book then this 站点。

大多数 C 编译器都有办法显示 translation unit.

的预处理形式

阅读 C 编译器的文档。

对于GCC, see this. For Clang, see that. The GCC preprocessor is documented here

如果您在文件 foo.c 上使用 gcc 作为 C 编译器 运行 it as gcc -C -E -H -Wall foo.c > foo.i to obtain into foo.i the preprocessed form of foo.c (but most comments are kept, and you could add more of them). Then look with a pager or an editor (I recommend GNU emacs, but feel free to use vim, gedit, more, less, most 等...) 到生成的 foo.i 文件.

I can see the macro does not output error messeges

一个宏没有输出任何消息,它只是展开了。

如果您使用 build automation tool such as GNU make or ninja,请阅读其文档。

如果您使用某些 IDE,请阅读其文档。许多 IDE 提供了一种显示预处理形式的方法,有些甚至可以动态展开宏。

不要忘记在一些情况下,你可能想使用一些其他预处理器,例如GPP or GNU m4, or generate some C code programmatically (like GNU bison, or lemon, or SWIG does). Many transpilers (such as Chicken-Scheme or Bigloo) are generating C code, and in Bismon I am doing that at run time, then dynamically loading the generated plugin, using dlopen(3). In RefPerSys we want to generate C++ code at runtime, and later use libgccjit.

您可以找到很多 open source C preprocessors, even standalone ones like mcpp. You could study their source code and improve them for your needs. And tinycc or nwcc 足够小的开源 C 编译器(内置预处理器),您可以在几天内对其进行改进。

如果您使用最近的 GCC, you could extend it with your own plugin,也许添加您自己的 _Pragma(在您的宏中使用),这将在预处理后显示内容。

一些聪明的调试器(例如适当配置的最近 GDB)能够进行宏扩展(然后用 gcc -O0 -g3 -Wall 编译你的 C 代码)

Bismon, I use lots of quite long C macros. They might be inspirational. And so is the container-macros library. And also GTK里面的宏可读性很好

我的经验:要调试宏,请从中删除很多行,直到对其扩展感到满意为止。逐步扩展该宏。在您的源代码上使用 git

$cc -pedantic a.c

我建议在 Linux 上使用 gcc -Wall -Wextra -g3 -H -pedantic a.c,然后使用最近的 gdb

一般来说您不能调试宏,因为宏不是被执行的东西。

But there should be the #define check(x).... So it does not tell me WHY is it missing.

您的宏已被预处理 - 即文本被预处理器替换。

预处理后扩展为:

    for(char c;({int _val = (read(fd,&c,1)); (_val ==-1? ({fprintf(
              stderr
              , "Error (" "./example.c" ":%d) -- " "%s\n",30,strerror(
              (*__errno_location ())
              )); exit(-1);-1;}) : _val); })>0;)
        fputc(c,
               stdout
                     );

您无法调试宏,因为高级调试器将逐步执行宏代码。你可以做的是暂时展开源代码中的宏,然后使用调试器查看问题所在。

一个想法是使用代码格式化程序,如 clang-format 来美化宏,以便您可以更好地理解它并使用调试器。应用这个想法,将宏转换为一个函数,让格式化程序完成它的工作:

$ clang-format -i macro.c

结果:

void check1(x) {
  ({
    int _val = (x);
    (_val == -1 ? ({
      fprintf(stderr,
              "Error (" __FILE__ ":%d) -- "
              "%s\n",
              __LINE__, strerror(errno));
      exit(-1);
      -1;
    })
                : _val);
  })
}

您最初发布的代码实际上可以在此处正确编译和执行。我制作了一个带有一些剪辑的版本以便能够测试宏:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define check(x) ({int _val = (x); (_val ==-1? \
    ({fprintf(stderr, "Error (" __FILE__ ":%d) -- " \
    "%s\n",__LINE__,strerror(errno)); \
    exit(-1);-1;}) : _val); })

int main (int argc, char **argv)
{
    check(-1);
    return 0;
}

结果:

$ gcc a.c && ./a.out
Error (a.c:13) -- Success

这可能有点晚了,但我想说的是你没有在你想要检查代码的地方放置一个检查(打开文件)。

改变这个:

fd = open(argv[1], O_RDONLY);

为此:

check(fd=open(argv[1], O_RDONLY));

这就是 Jacob 在其视频中所做的。他在读取操作中额外添加了检查,但这完全是次要的,目的是捕获文件成功打开后出现的错误(视频中没有显示此类错误的示例)。