C宏 - 动态#include

12

我在尝试使用GCC构建可变字符串来生成#include语句。

我的思路是,对于每个编写的源模块,我想要包含一个在之前的构建过程中动态生成的C源文件作为头文件。

生成这个文件没有问题。但是,包含它却很麻烦。

目前我已经有了如下代码(identities.h):

// identities.h

# define PASTER2(str)  #str
# define PASTER(str)   PASTER2(str ## .iden)
# define EVALUATOR(x)  PASTER(x)

# define IDENTITIES_FILE EVALUATOR(__FILE__)
# include IDENTITIES_FILE

理想情况下,应该这样使用(main.c):

//main.c

# include "identities.h"

int main() {return 0;}

这将在编译之前由预处理器一次性扩展,以产生:

//main.c (preprocessed)

# include "main.c.iden"

int main() {return 0;}

我使用的两级间接(PASTER和EVALUATOR)是这篇帖子的结果。

不幸的是,这不起作用,我得到了错误:

obj/win32/dbg/main.o
In file included from main.c:1:0:
identities.h:42:1: error: #include expects "FILENAME" or <FILENAME>

我认为问题在于 include 语句缺少引号.. 有任何想法吗?


1
如果你有一个“动态生成的C源代码”,那么你能否动态生成一个包含所有想要包含的头文件的新头文件呢?虽然我仍然很好奇是否可能做到你想做的事情。 - leetNightshade
3
简短回答:你不能这样做,没有任何例外。 - Alexandre C.
@leet:不,这个问题需要绝对的极简主义方法来修改当前的构建系统。我们已经达成共识,将尝试通过编辑每个文件来包含单个通用头文件“identities.h” - 只有那一个文件。 - J T
@AlexandreC。奇怪,我可以做到这一点。我必须不存在于您的宇宙中 ;-). 我想您的意思是您不能使用标准C/C++来实现这一点。 - artless noise
5个回答

9

这是在Linux源代码树中完成的;请参见旧编译器-gcc.h的第125行

#define __gcc_header(x) #x
#define _gcc_header(x) __gcc_header(linux/compiler-gcc##x.h)
#define gcc_header(x) _gcc_header(x)
#include gcc_header(__GNUC__)

我正在尝试使用GCC构建一个变量字符串来生成#include语句。
这个标记将__GNUC__的值粘贴到一个字符串中;"linux/compiler-gcc" __GNUC__ ".h",然后将结果字符串化。这可能是gcc预处理器的扩展。
以下是一个例子:

t1.h

#define FOO 10

t2.h

#define FOO 20

a.c

#ifndef VERSION
#define VERSION 1
#endif
#define __gcc_header(x) #x
#define _gcc_header(x) __gcc_header(t ## x.h)
#define gcc_header(x) _gcc_header(x)
#include gcc_header(VERSION)
#include <stdio.h>

int main(void)
{
        printf("FOO is %d\n", FOO);
        return 0;
}

这里有两个编译器,

g++ -o a a.cc
g++ -DVERSION=2 -o a a.cc

无论是编译的输出都会得到预期的结果。

与Linux源代码一样,您可以使用gcc预定义值。 echo | g++ -dM -E - 将给出一个列表。

对于您的情况,您可以使用makefile将定义传递给编译器,以允许动态包含生成的头文件而不更改源代码。但是,一个简单的替代方法就是在模板源文件上运行sed等命令,并将其替换为已知的包含名称。

任何一种技术都适用于生成测试夹具等。然而,对于编译器功能发现,这是一种更好的方法。对于使用IDE的程序员来说,这可能是他们唯一的选择。


1
正如其他人所指出的那样,你不能使用__FILE__。在C++和更新的C中,它是一个指针而不是字符串字面量(根据arelius)。 - artless noise
Linux 已经合并了文件,只需使用预处理条件进行检查即可。这是一个链接到旧版本的链接。另一个可能性是新GCC和clang中的has_include扩展 - artless noise
1
我解决了这个问题,在MSVC中需要使用## x而不是##x。空格会被删除。 - irvnriir
另一个可能性是新GCC和clang中的has_include扩展 - artless noise

3

我相当确定你不能做你想做的事情,__FILE__返回一个字符串,##操作符作用于标记,而没有CPP字符串连接预处理宏。通常情况下,这个问题可以通过两个连续的字符串来解决,例如:

"Hello" " World"

C++解析器将被视为单个字符串处理。然而,#include是预处理器的一部分,因此不能利用这一点。

旧答案:

你为什么要这样做?

{ #str, str ## .iden }

我确定那不是预处理器语法;你希望通过那做什么?你尝试过这样吗:

str ## .iden

您遇到的错误可能是由于大括号'{'引起的。

不幸的是,我之前尝试过后一种方法,但得到了相同的错误。我正在使用的方法来自GCC文档:http://tigcc.ticalc.org/doc/cpp.html#SEC18 - J T

1

关于Boost预处理库中的BOOST_PP_STRINGIZE怎么样?它是专门用来在名称周围添加引号的。


不幸的是,这只是在文本前添加一个井号:“# define BOOST_PP_STRINGIZE_I(text) #text”。 - J T
4
BOOST_PP_STRINGIZE不仅仅是添加井号#。文档特别指出,与仅使用#不同,该宏实际上允许其参数被展开(而#不允许)。经过几次测试,似乎您的主要问题在于__FILE__扩展为已经带有引号的文件名,而无法去除它们(至少据我所知)。 - Mikael Persson
@Mikael:你说得对,我也刚得出这个结论。 - J T
我建议你使用构建系统来解决这类问题,比如像cmake这样的工具,或者自己编写脚本。这就是Qt生成.ui文件的头文件的方式。 - Mikael Persson
2
是的,我已经转而使用GCC和Make:gcc -c -DEVALUTOR_ARGUMENT=$< $< - J T
哇,这很好而且简单!非常不错。 - Mikael Persson

1

暂且不考虑整个包含语法的问题,我不理解你的代码在试图做什么。你说:

# define PASTER(str)  { #str, str ## .iden }

你输入了 `main.c` 希望输出 `"main.c.iden"`,但实际上返回的是 `{ "main.c",main.c.iden }`。
你是不是想要这个?
#define PASTER2(str) #str
#define PASTER(str) PASTER2(str ## .iden)

你是正确的,我已经更改了它,但不幸的是它仍然产生相同的错误。 - J T

-1

你不能这样使用预处理器。你必须向 #include 指令提供一个文件名,它不能是其他的宏。


1
我知道这是不正确的(搜索“Computed Includes”):http://tigcc.ticalc.org/doc/cpp.html - J T
1
@JT 这是 GCC 预处理器的“特性”,它不是 C 或 C++ 预处理标准的一部分。 - user2100815
@JT,实际上他是完全正确的,计算包含是GCC扩展,并且您没有将问题标记为仅限于GCC。 - Blindy
2
抱歉没有标记,尽管我在问题中已经说明我正在使用GCC。 - J T
如果您想依靠GNU扩展,应该从您的宏中删除范围括号。但是尽量保持可移植性,这将会有所回报。 - ognian
5
实际上,“计算包含”(或类似的形式)是C标准——C99标准所允许的。请参见6.10.2/4……“#include pp-tokens newline”,其中pp-tokens必须扩展为<header>"header-file"中的一个。 - pmg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接