将源和标头包含到顶级C包装器中

Including sources and headers into a toplevel C wrapper

本文关键字:包装 包含      更新时间:2023-10-16

我偶然发现了以下代码:

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

这肯定是不寻常的,但它被认为是不良做法吗?如果是,为什么?

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

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

当然,将翻译单元包含在一个主翻译单元中(对我来说,这是滥用预处理器,但这不是重点)的主要好处很简单:编译时I/O更少,因此编译更快。这里已经解释过了。

不过,这只是故事的一个方面。这项技术并非完美。这里有几个注意事项。为了平衡"团结的魔力">文章,下面是《团结的邪恶》的文章

总之,这里有一个我反对的简短列表,还有一些例子:

  • 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文件)测试我的模块,并确保我没有添加任何错误。

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

这肯定是不寻常的,但它被认为是糟糕的做法吗?如果是,为什么?

您可能看到的是"Unity Build"。如果配置正确,Unity构建是一种不错的方法。最初将库配置为以这种方式构建可能会有问题,因为由于可见性的扩展,可能会出现冲突——包括作者打算对翻译保密的实现。

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

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

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