【问题标题】:Including sources and headers into a toplevel C wrapper将源代码和头文件包含到顶级 C 包装器中
【发布时间】:2014-02-24 15:40:52
【问题描述】:

我偶然发现了以下代码:

//
// 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

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

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

【问题讨论】:

  • 您将 C++ 文件包含在 extern "C" 块中...这不仅仅是不好的做法。此外:在不常见的情况下包含源文件,因为-尤其是对于较大的项目-,您编译单独的模块,并相应地链接它们。包含源文件使every 函数无处不在 可用。 externstatic 函数没有意义...这不仅效率低下,而且混乱且不安全并且使您的项目更难合作,这意味着您必须重新-每当你修复一个错误时编译很多
  • @EliasVanOotegem 为了记录,这不是我的代码!像这样的编译技术和全局命名空间污染是 Verilog/SystemVerilog 开发中的行业标准(尽管 I would claimbad)实践。代码其实是来自各大模拟器厂商写的一个验证库……我就是想全面了解一下出错的全部原因。
  • 好吧,我已经发布了您问题的答案。也许,这方面的另一个坏处是 extern "C"{,对我来说,暗示 C 代码,而不是暗示 C++ 代码的 .cc 文件。在某种程度上,您发布的代码是一个谎言(如在Java中:函数副作用是谎言),这很糟糕,mkay

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


【解决方案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 函数相似:两者都可以在任何地方调用
  • 调试将要求您构建所有内容,除非(如“pro” 文章建议的那样)为同一项目准备好 Unity-Build 和模块化构建. IMO 有点麻烦
  • 如果您希望从项目中提取一个库以供以后重复使用(考虑通用共享库或 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 文件)测试 my 模块,并确保我没有添加任何错误。

【讨论】:

    【解决方案2】:

    问题是每次包含标头时都会编译您的每个*.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" 的问题。

    【讨论】:

    • '...每次头文件...' OP 没有指定这些包含出现在头文件中。
    • 好点,我想我误解了他的问题。不过,我的编辑仍然有效,无论如何这都是不好的做法。
    • @πάνταῥεῖ:没关系,任何体面的标头都有一个包含保护,因此无论哪种方式,该声明都是错误的...那个,并且在标头中包含一个 C 文件,现在 会很奇怪
    • 当您在多个翻译单元中实现相同的功能时,包含守卫不会帮助您。当然,编译器不会介意,但是当链接器看到多个目标文件中定义了相同的符号时,它会出错。
    【解决方案3】:

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

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

    但是,如果extern "C" 块,定义(在*.cc 中)应该在外面。

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

    由于翻译计数下降,它减少了依赖性/复杂性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-10
      • 1970-01-01
      • 1970-01-01
      • 2022-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多