【问题标题】:Can you use environment variables in C/C++ include directives?您可以在 C/C++ 包含指令中使用环境变量吗?
【发布时间】:2015-01-11 13:27:15
【问题描述】:

假设我有这样的文件夹布局:

.
+-- 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 这样的工具可以做到这一点。或者这被认为是不好的做法?

【问题讨论】:

  • 通常你在你的makefile和-I选项中处理这样的问题来添加考虑搜索包含文件的路径。是的,使用 make(不是 CMake)。
  • 为什么不直接使用符号链接呢?非常适合这种情况。 Visual Studio 中缺乏支持(编辑器无法识别两个不同的路径是同一个文件)使得在 Windows 中编辑的文件不太实用,但是(1)您使用的是 Linux,并且(2)您'不会通过链接编辑文件。
  • @Cheersandhth.-Alf Aaahrg!我的一位同事用他的符号链接构建树系统惹恼了其他所有人。使用 make 有更好的方法(例如,vpath 就是其中之一)。
  • 您可以原则上使用宏作为标题名称,但我记得它很脆弱。我会在这个方向上接受任何建议,带着一粒盐。或更多。

标签: c++ linux include preprocessor-directive


【解决方案1】:

Weeelll...这是可能的,有点,但它并不漂亮,而且它有一些陷阱。通常最好在构建系统中添加包含路径,例如(假设是普通的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 源文件而不是标题?不要走那条路;在那条路上疯狂的谎言。单独编译源文件并以通常的方式将它们链接在一起,除非您有真的很好的理由不这样做。

所以,我看不出您为什么要在代码中的#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_PATHfoobar.h",所以我们需要另外一层来扩展PROJECT_PATH

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

这只是增加了pathname 参数被扩展的地方。最后,PROJECT_PATH#defined 变为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"(注意空格),这不起作用。

【讨论】:

  • 感谢您指出包含源文件的错误!这绝对是不正确的,我已经在上面解决了这个问题。但是,非常感谢您的深入回答。我会坚持做我认为的工具。
猜你喜欢
  • 2023-02-02
  • 1970-01-01
  • 1970-01-01
  • 2015-01-29
  • 2015-03-22
  • 2022-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多