“包含*.c文件”风格的C编程中可能存在的缺陷

3
我发现以下代码的方式如下:

//file.c  
#include <stdlib.h>

void print(void){

    printf("Hello world\n");
}

and

//file main.c  
#include <stdio.h>
#include "file.c"

int main(int argc, char *argv[]){

    print();

    return EXIT_SUCCESS;
}

这种编程风格有什么缺陷吗?虽然我感觉有,但我无法理解它的缺陷,因为在某个地方我读到把实现分离成*.h和*.c文件可以帮助编译器检查一致性。我不明白一致性是什么意思。
如果您有建议,我将不胜感激。
--谢谢

1
在代码前加上4个空格(或在编辑器工具栏中选择代码按钮)可以将其显示为代码。你不需要使用反引号来表示代码片段。 - Mehrdad Afshari
11个回答

9
没有任何限制阻止您包含 `.c` 文件。然而,将声明(在`.h`文件中)和实现(在`.c`文件中)分开,然后仅包含 `.h` 文件有以下几个优点:
- 编译时间。您的声明通常比您的实现更改较少。如果仅包括`.h`文件并对实现进行更改(在`.c`文件中),则只需重新编译一个`.c`文件,而不是包含修改文件的所有文件。 - 可读性和接口管理。您可以在(通常很小的)`.h`文件中一次性检查所有声明,而`.c`文件中充满了代码行。此外,它可以帮助您确定哪个文件看到哪些函数和变量。例如,避免在不想要的地方包含全局变量。

9

一般来说,人们希望编译器可以编译 .c 文件,而不是直接针对 .h 文件进行编译。通常情况下,.h 文件只会被包含在 .c 文件中。

因此,你的代码应该通过像这样的方式进行编译:

gcc main.c file.c 

不要仅仅使用gcc main.c命令。在链接阶段,该命令会看到重复的符号并失败。


6
如果在多个源码文件中包含file.c,然后将它们组合成一个库/可执行文件,你将会遇到问题,因为你会有重复的方法实现。以上方式对于分享和重用代码来说不是很好,不建议使用。

2

是的,这是允许的。

使用这个功能是一个高级话题。

  • 它会减慢开发编译时间(只编译必要的内容更便宜)。
  • 它会加快部署编译时间(所有文件都已过期)。
  • 它允许编译器跨模块边界内联函数。
  • 它允许使用一种技巧来控制库中导出的符号,同时保持其模块化。
  • 它可能会使调试器混淆。

2

如果将数据与代码分离更加方便,那么在另一个文件中包含数据并不罕见。例如,可以通过在char数组中包含XPM或原始BMP数据来嵌入图像到程序中。如果数据是从另一个构建步骤生成的,则将其包含到文件中是有意义的。

我建议使用不同的文件扩展名以避免混淆(例如*.inc、*.dat等)。


0
在.h文件中,您应该放置函数原型。例如,在您的代码中应该有:
//file.h
void print(void);

//file.c
void
print(void)
{
   printf("Hello world\n");
}
//file main.c  
#include <stdio.h>
#include "file.h"

int main(int argc, char *argv[]){

    print();

    return EXIT_SUCCESS;
}

1
file1.c 是一个示例,展示了在包含文件中存在可执行代码而不是实际使用的程序的要点。 - CW Holeman II
是的,我现在再次阅读他的问题时注意到了。我读到了 #include "file.h",但没有注意到真正的问题。我会删除我的回答中的那部分。谢谢。 - Macarse

0

这是一种不好的代码风格,但另一个原因是它可以作为一种技巧的一部分,使用 ## 运算符 来进行一种类似于C语言中的贫民版模板。

请注意,这种方法非常危险,不建议使用,会导致代码难以调试和维护,但你确实可以像下面这样做:

mytemplate.c:

MyTemplateFunction_ ## MYTYPE(MYTYPE x)
{
  // function code that works with all used TYPEs
}

main.c:

#define MYTYPE float
#include "mytemplate.c"
#undef MYTYPE

#define MYTYPE int
#include "mytemplate.c"
#undef MYTYPE

int main(int, char*)
{
  float f;
  int i;
  MyTemplateFunction_float(f);
  MyTemplateFunction_int(i);
  return 0;
}

0

宏的弊端可能会加剧:

file1.c

#define bottom arse

file2.c

int f()
{
    int arse = 4;
    bottom = 3;
    printf("%d", arse);
}

main.c

#include "file1.c"
#include "file2.c"

void main()
{
    f();
}

确实是一个复杂的例子。但通常你不会注意到它,因为宏的作用域是它所在的文件。

我确实遇到过这个 bug,我正在将一些库代码导入到一个新项目中,但懒得编写 Makefile,所以我只生成了一个 all.cpp 文件,其中包含了库中所有的源代码。由于宏污染,它并没有按预期工作。花了我一段时间才弄清楚。


0

从 C 语言的角度来看,在程序中包含 .c 文件是没有问题的。C 语言并不关心文件的扩展名,事实上,C++ 经常省略某些头文件的扩展名以避免冲突。

但从程序员的角度来看,这确实很奇怪。大多数程序员会假定不会包含 .c 文件,这可能会导致错误的假设而引起问题。最好避免这种做法。如果你发现必须使用它,那就意味着设计不良。


0

我知道只有两个合理的原因可以包含C文件:

  • 非平凡的内联函数,但这实际上是一种风格问题
  • 通过在多个其他文件中包含相同的文件来共享私有(静态)函数的实现。这实际上是以纯平台无关的方式进行操作的唯一方法(但如果可用,则像gcc的隐藏属性等工具链特定技巧要好得多)

缺点:

  • 您会多次编译相同的代码
  • 如果不谨慎使用,很快就会导致公共符号的多个定义,以一种难以调试的方式(包括其他文件的包含文件...)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接