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

时间:2015-01-11 13:27:15

标签: c++ linux include preprocessor-directive

说我有这样的文件夹布局:

.
+-- 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 个答案:

答案 0 :(得分:6)

Weeelll ......有可能,有点但是它不漂亮,而且有一些陷阱。通常最好在构建系统中添加include路径,例如(假设为普通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"。值得注意的是,宏参数未展开,因此即使定义了宏HEADER_STRING(PROJECT_PATH)"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 #define d到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"(注意空格),这不起作用。