【问题标题】:Figuring out multiple file compilation with make & C用 make & C 搞清楚多文件编译
【发布时间】:2011-10-05 02:13:30
【问题描述】:

对于一个项目,我正在与一位合作伙伴一起编写决策树实现。由于我们俩都是 C 的新手并且必须快速工作,因此我们基本上将所有功能转储到一个文件中,最终超过 1600 行。这是一个快速而肮脏的项目,但现在下一个任务让我们负责扩展和重新实现代码。在目前的情况下,这不会发生。

现在,我正在根据功能责任分解原始来源。问题是,许多功能交织在一起,我的 make 文件出现了重大错误。更具体地说,其他源文件正在报告在单独文件中声明的函数的隐式声明。

我真的没有使用多个文件 makefile 的经验。当前的语法是从去年系统编程课程中的一个简单的 shell 实现中借用的,尽管这个当前的项目在复杂性上要高一个数量级。

cc= gcc
CFLAGS= -g -Wall -lm

proj2: main.o split.o tree.o id3.o output.o 
  $(CC) $(CFLAGS) -o proj2 main.o split.o tree.o id3.o output.o

我也尝试过之前的版本,其中每个目标文件都是单独编译的,比如

main.o: main.c split.c tree.c id3.c output.c
  $(CC) $(CFLAGS) -o main.c split.c tree.c id3.c output.c

然后重复此过程,为每个源创建一个.o 文件,然后将其编译为可执行文件。

但是,这不起作用,我收到了大约 500 行编译器投诉和警告,主要是关于隐式函数声明。

所以,基本上我有两个相关的问题:

  • 是否可以在不同的源文件之间交织函数调用?
  • 如果是这样,我怎样才能在这里实现?

【问题讨论】:

  • 这是-o main.c 错字吗?它将用编译器的输出覆盖文件main.c

标签: c compiler-errors makefile


【解决方案1】:

首先介绍一下您的 makefile。

proj2: main.o split.o tree.o id3.o output.o 
  $(CC) $(CFLAGS) -o proj2 main.o split.o tree.o id3.o output.o

这应该可以工作(如果代码是正确的),但如果你使用的是 GNUMake(你应该这样做),你可以整理一下:

proj2: main.o split.o tree.o id3.o output.o 
  $(CC) $(CFLAGS) -o $@ $^

现在您只需维护一份对象列表副本。

另一个版本是错误的:

main.o: main.c split.c tree.c id3.c output.c
  $(CC) $(CFLAGS) -o main.c split.c tree.c id3.c output.c

首先,您试图将所有源文件编译成一个目标文件,这有悖于目标文件的目的。其次,您将一个目标文件命名为main.o,而该名称应该真正属于由main.cc 制成的目标文件。第三,该命令告诉编译器将所有其他源文件(split.ctree.c、...)编译成一个名为“main.c”的目标文件——这不是非法的,但你一定会绊倒自己。

此外,您应该尝试使用 C++,而不是 C,但那是另一天的事了。

现在拆分Big Ball of Mud。我假设您知道如何将大函数分解为较小的函数,因此问题是将函数分离到不同的源文件中(然后正确编译和链接它们)。假设main()调用了一个函数foo()

/* main.c */

void foo()
{
  // do foo things
}

int main()
{
  // do main things
  foo();
  return(0);
}

如您所知,foo 必须先出现,否则当main 试图调用未声明的函数时,编译器会停止。但是我们可以事先声明foo

/* main.c */

void foo();

int main()
{
  // do main things
  foo();
  return(0);
}

void foo()
{
  // do foo things
}

当编译器到达对foo()的调用时,它已经知道存在这样的函数,并相信我们稍后会定义它。现在的诀窍是:如果我们指示编译器进行编译,而不是链接(即生成像 main.o 这样的目标文件,而不是像 proj2 这样的可执行文件),它会更信任我们:

/* main.c */

void foo();

int main()
{
  // do main things
  foo();
  return(0);
}

这将很好地编译成main.o。当我们将事物链接到可执行文件中时,编译器相信我们会在其他目标文件中提供void foo() 的定义。定义将在另一个文件中,如下所示:

/* foo.c */

void foo()
{
  // do foo things
}

我们可以手动构建:

gcc -g -Wall -lm -c foo.c -o foo.o
gcc -g -Wall -lm -c main.c -o main.o
gcc -g -Wall -lm foo.o main.o -o proj2

但这很快就会变得乏味,所以我们将编写一个makefile:

cc= gcc
CFLAGS= -g -Wall -lm

proj2: main.o foo.o 
  $(CC) $(CFLAGS) -o $@ $^

%.o: %.c
  $(CC) $(CFLAGS) -c -o $@ $<

到目前为止一切顺利。如果这很清楚,那么我们可以继续处理头文件...

【讨论】:

    【解决方案2】:

    您需要为每个源代码创建头文件以在其中包含声明。然后在源代码的顶部#include 适当的头文件。

    【讨论】:

      猜你喜欢
      • 2011-02-05
      • 1970-01-01
      • 1970-01-01
      • 2021-11-23
      • 2020-01-29
      • 1970-01-01
      • 2021-12-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多