【问题标题】:How does gcc handle local included files?gcc 如何处理本地包含的文件?
【发布时间】:2016-02-16 04:08:46
【问题描述】:

这真的不应该是我必须问的问题,但不知何故,通过搜索回答我的问题,我什么也找不到。

为了再问一个问题,我做了三个文件:main.c、sub.c 和 sub.h。 main.c 有'main'函数,而 sub.c 只包含函数定义。

最初,main.c 将 '#include "sub.h"' 作为其唯一的包含语句。

尝试“gcc main.c -O3 -o test”会导致错误,指出函数 f()(在 sub.h 中声明,在 sub.c 中定义,并在 main.c 中引用)未被引用。 尝试“gcc main.c sub.c -O3 -o test”会导致预期的行为。

然后我修改了 test.c,删除了 #include 和对 f 的引用。 'gcc main.c -O3 -o test2' 按预期工作。

然后我重新添加了对 f 的引用,却忘记重新添加#include。尽管如此,“gcc main.c sub.c -O3 -o test3”仍按预期工作。

我注意到了这个错误,并故意将包含重新添加为“#include sub.c”。 'gcc main.c sub.c -O3 -o test4' 导致错误,说 f() 被定义了多次。 'gcc main.c -O3 -o test4 恢复正常工作。

我可以从中得出的唯一结论是,就本地文件而言,如果文件是源代码文件,则包含它并且不要将其添加到命令中,否则将其源代码添加到命令中并不要费心包含它,因为显然你是否包含它并不重要。我猜?

基本上,我的问题是,上述行为是否有意,如果是,是否记录在案,在哪里,以便我可以阅读它并就将来如何处理我的包含文件做出更明智的决定。

【问题讨论】:

  • 一般来说,#include .c 文件是个坏主意。作为新手 C 程序员不要这样做。即使作为一个有经验的程序员,也没有多少情况下可以这样做。
  • @kaylum #include .c 文件始终是“有效的”,但很少是“必要的”。但是,“很少”不是“从不”。在我的特定情况下,包括 .c 文件允许编译器比 .h 文件优化得更好,并且还允许我轻松更改程序结构的一部分,尽管它在编译时保持不变,但在构建之间变化很大我的程序结构井井有条。
  • 好的,然后回到你的问题。你到底在问什么? “本地包含的文件”是什么意思?您的意思是与源代码在同一目录中吗?如果是这样,就 gcc 而言,唯一的区别是它在哪里查找该文件。它不会改变编译或链接阶段。所以不清楚你在问什么。您的问题是您声明了两个 f 副本。一次是通过 C 文件的包含获得的 main.c,一次是您显式链接的 C 文件本身。
  • 明显有更多不同;如果我说,#include ,并使用在 stdlib.h 中声明的函数,那么它可以工作。当我#included "sub.h" 并使用在 sub.h 中声明的 f 时,它不起作用。我需要在命令起作用之前将 sub.c 添加到命令中,并且添加 sub.c 似乎否定了对#include 的需要。我的问题是,我在问题中描述的结果是否异常,或者是否符合预期,如果我想知道,我会读什么?
  • 你对编译过程的理解显然有些差距。 #include <stdlib.h> 不能单独工作。它之所以有效,是因为编译器隐式链接到标准 C 库 libc.so。后一步是您自己的 sub.h 示例中缺少的。在编译阶段,头文件 include 告诉 C 编译器“f 函数在某处定义,相信我”。在链接阶段,链接器需要找到实际定义。在标准库案例中,它位于 libc.so 中。在您的情况下,它在 sub.o 和 main.o 中都可以找到。

标签: c gcc include


【解决方案1】:

然后我重新添加了对 f 的引用,忘记重新添加#include。尽管如此,“gcc main.c sub.c -O3 -o test3”仍按预期工作。

对于“工作”的适当松散定义;我敢打赌f() 返回一个int,而gcc 默认为C89 模式。

在 C99 之前,如果编译器在看到函数定义或声明之前遇到函数调用,则假定被调用函数返回 int。因此,只要f() 实际上返回一个int,您的代码就会编译并运行成功。如果f() 返回int,代码仍然可以编译,但您会遇到运行时问题。链接器所关心的是符号是否存在;它不关心类型不匹配。

C99 取消了隐式 int 类型,因此在 C99 编译器下,如果您在范围内没有 f() 的声明(通过包含 sub.h 或手动添加声明,您的代码将无法编译)。

我可以从中得出的唯一结论是,就本地文件而言,如果文件是源代码文件,则包含它并且不要将其添加到命令中,否则将其源代码添加到命令中并不要费心包含它,因为显然你是否包含它并不重要。我猜?

这是完全错误的结论。您希望将.c 文件包含在另一个.c 文件中作为常规做法,因为它可能导致各种混乱。 main.c 中的所有内容对 sub.c 都是可见的,反之亦然,从而导致潜在的命名空间冲突 - 例如,两个文件都可以定义一个名为 foo() 的“本地”帮助函数。通常这样的“本地”函数在源文件之外是不可见的,但是通过在另一个源文件中包含一个源文件,foo() 的两个版本都是可见的并且相互冲突。另一个问题是,如果一个.c 文件包含另一个.c 文件,该文件包含另一个.c 文件等,您可能会得到一个太大而编译器无法处理的翻译单元。每次在不需要的地方更改一个或另一个文件时,您都会重新编译 both 文件。这只是不好的做法

正确的做法是分别编译 main.csub.c 并确保 sub.h 包含在 both 中(您希望在 sub.c 中包含 sub.h 以确保您的声明与您的定义一致;如果不符合,编译器会在翻译sub.c) 时发出警告。

编辑

在 cmets 中回答以下问题:

当您说分别编译 main.c 和 sub.c 时,我假设您的意思是分别从它们中制作目标文件,然后链接它们(总共 3 个命令)?有没有办法用一个命令来做到这一点?

命令gcc -o test main.c sub.c 做同样的事情,它只是不将相应的目标文件保存到磁盘。您还可以创建一个简单的 Makefile,如下所示:

CC=gcc
CFLAGS=-O3 -std=c99 -pedantic -Wall -Werror

SRCS=main.c sub.c
OBJS=$(SRCS:.c=.o)

test: $(OBJS)
        $(CC) -o $@ $(CFLAGS) $(OBJS)

clean:
        rm -rf test $(OBJS)

那么你需要做的就是输入make test:

[fbgo448@n9dvap997]~/prototypes/simplemake: make test
gcc -O3 -std=c99 -pedantic -Wall -Werror   -c -o main.o main.c
gcc -O3 -std=c99 -pedantic -Wall -Werror   -c -o sub.o sub.c
gcc -o test -O3 -std=c99 -pedantic -Wall -Werror main.o sub.o

从 .c 文件构建目标文件有一些隐含的规则,因此您不需要在 Makefile 中包含这些规则。您需要做的就是指定目标和先决条件。

您可能需要删除-pedantic 标志才能使用某些特定于平台的实用程序,并且您可能还需要指定不同的标准(c89、gnu89 等)。不过,您肯定希望保留 -Wall -Werror 标志 - 它们将启用所有警告并将所有警告视为错误;他们会强制你处理警告。

【讨论】:

  • 谢谢你,我想你在这里回答了我所有的问题。 f 实际上返回 void,但我认为您的解释同样很好地涵盖了这种情况。关于包含 C 文件,我这样做是有原因的,并且可以完全控制我正在使用的所有 c 文件,这就是我要说的。当您说分别编译 main.c 和 sub.c 时,我假设您的意思是分别用它们制作目标文件,然后链接它们(总共 3 个命令)?有没有办法用一个命令做到这一点?
【解决方案2】:

如果你编译一个程序,那么 main 函数应该在那里。如果你编译一个没有 main 的程序,那么你只能生成目标文件。在您的情况下,看起来 main.c 包含 main() 并且 sub.c 包含一些函数定义。另一个重要的事情是当你定义一个头文件时,确保你有预处理器指令来防止多次包含头文件。 一个例子在这里:

http://www.tutorialspoint.com/cprogramming/c_header_files.htm

【讨论】:

  • 是的,这些我都知道。我确实应该指定 main.c 包含一个 main 并且 sub.c 只包含函数定义。我的问题几乎与 C 语言或一般包含无关,仅与 gcc 如何在包含的文件存储在本地的地方实现包含有关,
  • 本地或系统定义的标头之间没有区别。这类似于在源代码中添加包含文件的内容,并且可能对所有编译器都是相同的。
  • 再一次,你在谈论一般的包容性。我专门询问 gcc 的实现——它显然不像你期望的那样简单,正如我在问题中列出的结果所证明的那样。
  • @P...:你误解了你的结果。它与 gcc 如何包含文件无关,与旧版本的 C 如何处理隐式声明有关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多