将源和标头包含在顶层C包装器中

时间:2014-02-24 15:40:52

标签: c++ c coding-style include build-process

我偶然发现了以下代码:

//
// Top-level file that includes all of the C/C++ files required
//
// The C code may be compiled by compiling this top file only,
// or by compiling individual files then linking them together.

#ifdef __cplusplus
extern "C" {
#endif

#include <stdlib.h>
#include "my_header.h"
#include "my_source1.cc"
#include "my_source2.cc"

#ifdef __cplusplus
}
#endif

这绝对是不寻常的但它被认为是不好的做法,如果是这样,为什么?

我能想到的一个潜在的负面影响是,典型的构建系统难以分析依赖关系。还有其他原因导致这种技术没有被广泛使用吗?

3 个答案:

答案 0 :(得分:1)

首先关闭:extern "C" { #include "my_cpp_file.cc" }只是不加起来...无论如何,我会尝试用一个实际的例子回答你的问题。
请注意,有时您执行会在源文件中看到#include "some_file.c"。通常这样做是因为另一个文件中的代码正在开发中,或者不确定该文件中正在开发的功能是否会发布。
另一个原因很简单:提高可读性:不必滚动太多),甚至:反映你是线程。对某些人来说,将孩子的代码放在一个单独的文件中会有所帮助,特别是在学习线程时。

当然,将翻译单元纳入一个主翻译单元(对我而言,滥用预处理器,但这不是重点)的主要好处很简单:编译时I / O更少,因此更快汇编。 It's all been explained here

但是,这是故事的一个方面。这项技术 完美。这里有几个注意事项。只是为了平衡“统一构建的魔力”文章,here's the "the evils of unity builds" article

无论如何,这是我的反对意见的简短列表,以及一些例子:

  • static全局变量(说实话,我们都使用过它们)
  • externstatic功能相似:两者都可以随处调用
  • 调试需要您构建所有内容,除非(正如“专业”文章建议的那样)同时为同一项目准备了Unity-Build和模块化构建。 IMO有点冒险
  • 如果您希望从以后重新使用的项目中提取lib(想想通用共享库或DLL),则不适用

只是比较这两种情况:

//foo.h
struct foo
{
    char *value;
    int checksum;
    struct foo *next;
};

extern struct foo * get_foo(const char *val);

extern void free_foo( struct foo **foo);

//foo.c
#include <foo.h>
static int get_checksum( const char *val);
struct foo * get_foo( const char *val)
{
    //call get_checksum
    struct foo *retVal = malloc(sizeof *retVal);
    retVal->value = calloc(strlen(val) + 1, 1);
    retVal->cecksum = get_checksum(val);
    retVal->next = NULL;
    return retVal;
}
void free_foo ( struct foo **foo)
{
    free(*foo->value);
    if (*foo->next != NULL)
        free_foo(&(*foo->next));
    free(*foo);
    *foo = NULL;
}

如果我要将此C文件包含在另一个源文件中,get_checksum函数也可以在该文件中调用。在这里,这是的情况 名称冲突也会更加普遍。

想象一下,如果你编写了一些代码来轻松执行某些快速的MySQL查询。我会编写自己的头文件和源文件,然后像这样编译它们:

gccc -Wall -std=c99 mysql_file.c `mysql_config --cflags --libs` -o mysql.o

只需在其他项目中使用mysql.o编译文件,只需将其链接起来:

//another_file.c
include <mysql_file.h>

int main ( void )
{
    my_own_mysql_function();
    return 0;
}

然后我可以这样编译:

gcc another_file.c mysql.o -o my_bin

这节省了开发时间,编译时间,并使您的项目更易于管理(前提是您了解了make文件的方法)。

这些.o文件的另一个优点是在项目上进行协作时。假设我要为我们的mysql.o文件宣布一项新功能。在我处理代码时,所有将代码作为依赖项的项目都可以安全地继续使用上一个稳定编译的mysql.o文件。
完成后,我们可以使用稳定的依赖项(其他.o文件)测试我的模块,并确保我没有添加任何错误。

答案 1 :(得分:0)

问题是每次包含标题时都会编译每个*.cc文件。

例如,如果你有:

// foo.cc:

// also includes implementations of all the functions
// due to my_source1.cc being included
#include "main_header.h"

// bar.cc:

// implementations included (again!)
// ... you get far more object code at best, and a linker error at worst
#include "main_header.h"

无关,但仍然相关:有时,当您的标头包含C ++代码中的C stdlib标头时,编译器会遇到麻烦。

编辑:如上所述,还存在围绕C ++源extern "C"的问题。

答案 2 :(得分:0)

  

这绝对是不寻常的,但它被认为是不好的做法,如果是这样,为什么?

您可能正在查看"Unity Build"。如果配置正确,Unity构建是一种很好的方法。配置最初以这种方式构建的库可能会有问题,因为可能会因扩展的可见性而产生冲突 - 包括作者打算将其作为翻译专用的实现。

但是,如果*.cc阻止,则extern "C")中的定义应该在外面。

  

我能想到的一个潜在的负面影响是,典型的构建系统难以分析依赖关系。是否还有其他原因导致该技术未被广泛使用?

它降低了依赖性/复杂性,因为翻译计数下降了。