你能在C/C++的include指令中使用环境变量吗?

5

假设我有以下的文件夹结构:

.
+-- Project
    +-- src
        +-- foo.h
        +-- foo.cpp
    +-- test
        +-- test_foo.c

test_foo.c 看起来像这样:

#include "../src/foo.h"
#include <stdio.h>
#include <assert.h>

int main() {
    assert(foo() == true);
    printf("Test complete");
    return 0;
}

有没有办法用一个变量替换掉这一行#include "../src/foo.h",使之指向源代码目录?例如,在我的环境中我有一个变量:

PROJECT_SRC="./Project/src/"

然后,我可以这样编写include指令:

#include "PROJECT_SRC/foo.h"

如果我能拥有一个bash脚本,可以导出我在特定项目中需要的所有路径,那将会非常好。此外,如果该文件包含在不同的测试和构建文件中,我将不得不为每个文件设置相对路径(虽然不是很麻烦),这将不如一个绝对路径稳健。

另一种选择可能是像CMake这样的工具可以做到这一点。或者这被认为是不好的实践吗?


1
通常情况下,您可以在makefile中处理这个问题,并使用“-I”选项添加要搜索包含文件的路径。是的,请使用make(而不是CMake)。 - πάντα ῥεῖ
为什么不使用符号链接呢?对于这种情况非常有效。Visual Studio 缺乏支持(编辑器无法识别两个不同路径是同一文件),这使得对于在 Windows 中进行编辑的文件不太实用,但是 (1) 您正在使用 Linux,并且 (2) 您不会通过链接编辑该文件。 - Cheers and hth. - Alf
@Cheersandhth.-Alf Aaahrg!我的一个同事用他的符号链接构建树系统惹恼了其他人。使用make有更好的方法来完成它(例如,vpath)。 - πάντα ῥεῖ
你可以“原则上”使用宏来表示标头名称,但我记得它非常脆弱。我会谨慎接受在这方面的任何建议。或者更多。 - Cheers and hth. - Alf
1个回答

10

嗯...这是可能的,有点复杂,而且存在一些问题。通常最好在构建系统中添加包含路径,例如(假设使用纯make):

# C PreProcessor flags. This variable is used by make's implicit rules for 
# everything preprocessor-related.
CPPFLAGS += -I$(PROJECT_PATH)

在源文件中不带路径引用头文件,只需使用 #include 引用即可。这将使得 make 命令使用 -Iyour/project/path 选项调用编译器,从而让编译器在 your/project/path 目录下寻找头文件。也就是说,在 Makefile 文件中你可以写:

PROJECT_PATH = foo/bar
CPPFLAGS = -I$(PROJECT_PATH)

并且在这些来源中

#include "foobar.h"

具有 #include "foo/bar/foobar.h" 的效果。

...还有,我看到你尝试使用 #include 引用源文件而不是头文件了吗?不要走那条路;那条路只会导致疯狂。除非你有一个非常好的理由,否则将源文件分别编译并像往常一样链接它们在一起。

所以,我不明白为什么你想在代码中直接引用项目���径的原因;构建系统方面唯一的更改是你必须传递-DPROJECT_PATH = foo / bar / 而不是 -IPROJECT_PATH = foo / bar / ,而且这种机制比实际设计用于此类东西的机制更加脆弱。但如果你真的想这么做,那么以下是方法:

你遇到的第一个问题是

#include "foo/bar/" "baz.h" // no dice.

代码格式不正确,因此简单的方法行不通。我们需要尝试预处理器魔法,其工作原理如下:

#define HEADER_STRING(s) #s
#define HEADER_I(path, name) HEADER_STRING(path ## name)
#define HEADER(path, name) HEADER_I(path, name)

//                                v-- important: no spaces allowed here!
#include HEADER(PROJECT_PATH,foobar.h)

也许从下往上开始:

#define HEADER_STRING(s) #s

将其参数转换为字符串。也就是说,HEADER_STRING(foo/bar/baz.h)将扩展为"foo/bar/baz.h"。值得注意的是,宏参数不会被扩展,因此即使定义了宏PROJECT_PATHHEADER_STRING(PROJECT_PATH)也会扩展为"PROJECT_PATH"。这是当您尝试使用预处理器进行复杂操作时经常遇到的问题之一,解决方法是添加另一个层次,以便可以扩展参数:

#define HEADER_STRING_I(s) #s
#define HEADER_STRING(s) HEADER_STRING_I(s)

......对于HEADER_STRING我们不需要这个,但是在HEADER中会用到,所以记住这个技巧。我担心预处理器的替换规则有些晦涩难懂,详细解释已超出了SO答案的范畴。简而言之,宏是逐层展开的,当宏没有被展开时,通常的技巧是为它们提供一个展开的位置,也就是添加另一层。

接下来是HEADER_I

#define HEADER_I(path, name) HEADER_STRING(path ## name)

将其参数粘贴在一起并将它们传递给HEADER_STRINGHEADER_I(foo,bar)扩展为HEADER_STRING(foobar)。由于我上面提到的问题,HEADER_I(PROJECT_PATH,foobar.h)扩展为HEADER_STRING(PROJECT_PATHfoobar.h),进而扩展为"PROJECT_PATHfoobar.h",因此我们需要另一个层次来扩展PROJECT_PATH

#define HEADER(path, name) HEADER_I(path, name)

这只是添加了可以扩展pathname参数的位置。最后,将PROJECT_PATH定义为foo/bar/HEADER(PROJECT_PATH,foobar.h)扩展为"foo/bar/foobar.h",然后我们可以说

#include HEADER(PROJECT_PATH,foobar.h)

导入 #include "foo/bar/foobar.h"。可以在makefile里设置PROJECT_PATH并使用-DPROJECT_PATH=$(some_make_variable)传递。

最后需要注意的是,千万不能让任何空格跑到标记之间。

#include HEADER(PROJECT_PATH,foobar.h)

最终扩展为"foo/bar/ foobar.h"(注意空格),这是行不通的。


感谢您指出包含源文件的错误!那绝对不正确,我已经进行了修正。非常感谢您提供的深入答案。不过我会坚持使用我认为合适的工具。 - shiveagit
为什么这个不起作用?#define _addQuotes( v ) #v<<newline>>#define _relativeFromProject( v ) _addQuotes(D:\temporal\##v)<<newline>>#include _relativeFromProject(init.h) - irvnriir

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