如何让 make 找出正确的依赖关系以链接到正确的下游目标文件?

发布于 2024-09-25 16:36:27 字数 1342 浏览 4 评论 0 原文

我将使用一个小例子作为参考。考虑一个具有以下内容的项目:

inner_definitions.o : inner_definitions.cpp inner_definitions.h
    gcc $^ -o $@

inner_class_1.o : inner_class_1.cpp inner_class_1.h inner_definitions.h
    gcc $^ -o $@    

inner_class_2.o : inner_class_2.cpp inner_class_2.h inner_definitions.h
    gcc $^ -o $@

outer_class.o : outer_class.cpp outer_class.h inner_class_1.h inner_class_2.h
    gcc $^ -o $@

executable.o : executable.cpp executable.h outer_class.h
    gcc $^ -o $@

executable : __?1__
    __?2__

但是填充链接器依赖项的空白 __?1__ 和链接器命令的 __?2__ 并不容易。

在这个小例子中,人们可能会说很容易看出 __?1__ = inside_definitions.o inside_class_1.o inside_class_2.o outer_class.oexecutable.o 。然而,这显然不是一个可扩展的解决方案,因为它迫使每个开发人员了解他们正在使用的代码的所有依赖关系,以便他们可以手动找出依赖关系,而不是使用 make 实用程序。

另一种解决方案是为每个目标文件设置一个不同的变量,列出其所有下游依赖项:即 __?1__ =executable.o $(executable_dependencies)。这不是一个理想的解决方案,因为它强制 makefile 以特定方式编译,因此变量仅在完全定义时才使用。此外,对于非常大的应用程序,这些变量可能会超过最大变量长度。

另一种解决方案是使用存档 .a 文件进行链接。在这种情况下,我们可以构造一个包含 inner_defintions.oinner_class_1.oinner_class_1.a,因此它可以与任何需要 inner_class_1.o 的对象文件,而不强制开发人员重建依赖关系。这种方法看起来很有前途,但涉及到许多重复的文件。此外,gcc 链接器似乎无法处理嵌套的存档文件。

还有另一种方法吗?最好的方法是什么? gcc 链接器可以处理嵌套归档文件吗?

I'm going to use a small example for reference. Consider a project with:

inner_definitions.o : inner_definitions.cpp inner_definitions.h
    gcc $^ -o $@

inner_class_1.o : inner_class_1.cpp inner_class_1.h inner_definitions.h
    gcc $^ -o $@    

inner_class_2.o : inner_class_2.cpp inner_class_2.h inner_definitions.h
    gcc $^ -o $@

outer_class.o : outer_class.cpp outer_class.h inner_class_1.h inner_class_2.h
    gcc $^ -o $@

executable.o : executable.cpp executable.h outer_class.h
    gcc $^ -o $@

executable : __?1__
    __?2__

But filling in the blanks __?1__ for the linker dependencies and __?2__ for the linker command isn't easy.

In this small example, one could argue that its easy to see that __?1__ = inner_definitions.o inner_class_1.o inner_class_2.o outer_class.o executable.o . However, this is clearly not a scalable solution as it forces each developer to understand all the dependencies of the code they are working with so they can figure out the dependencies by hand rather than by using the make utility.

Another solution would be to have a different variable for each object file that listed all its downstream dependencies: i.e __?1__ = executable.o $(executable_dependencies). This is not a desired solution because it forces the makefile to be compiled in the specific way so the variables are only used when they are fully defined. Also, for really large applications these variables might exceed the maximum variable length.

Yet another solution is to use archive .a files for linking. In this case, we could construct an inner_class_1.a that included both inner_defintions.o and inner_class_1.o, so it could be linked with any object file that needed inner_class_1.o without forcing the developer to reconstruct the dependencies. This approach seems promising, but involves having many duplicate files. Also, it doesn't appear that the gcc linker can handle nested archive files.

Is there another approach? What is the best approach? Can the gcc linker handle nested archive files?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

初心未许 2024-10-02 16:36:27

您尝试自动化的工作(选择正确的目标文件以满足所有引用)通常留给链接器,使用静态库(“.a”文件)对候选目标文件进行分组,就像您建议的那样。

您可能会忽略一个重要的细节:如果您向链接器传递一个存档,它只会链接该存档中实际需要的那些文件。因此,您可以以相当粗的粒度创建档案,而不必膨胀所有可执行文件——链接器将只选择它需要的内容——尽管如果您采取这种方法太过分,很容易导致不必要的缓慢构建。

GNU 链接器不会从嵌套库中提取对象。如果您想通过合并许多小库来创建一个大库,可以使用 ar 脚本。这将为您提供一个包含所有目标文件的单个存档,而无需任何嵌套库结构。

如果重复拥有包含相同目标代码的 .o 文件和 .a 文件让您感到困扰,精美的手册描述了一种“直接”更新档案的方法

The job you're trying to automate (picking the right object files to satisfy all references) is usually left to the linker, using static libraries (".a" files) to group the candidate object files, just as you suggest.

An important detail you may be missing: If you pass the linker an archive, it will only link in those files from the archive that are actually needed. So you can create archives at a fairly coarse level of granularity without necessarily bloating all your executables -- the linker will pick just what it needs -- although can easily end up with needlessly slow builds if you take this approach too far.

The GNU linker will not pull objects out of nested libraries. If you want to make one big library by merging many small ones, you can do that with the "addlib" command in an ar script. That will give you a single archive containing all of the object files without any nested library structure.

If the duplication of having .o files and .a files containing the same object code lying around bothers you, the fine manual describes a way to have make update the archives "directly".

浅沫记忆 2024-10-02 16:36:27

你的 makefile 必须有一个链接在一起的对象列表,如下所示:

OBJ_FILES = inner_definitions.o inner_class_1.o inner_class_2.o \ 
outer_class.o executable.o

executable : $(OBJ_FILES)
    gcc $^ -o $@

必须有人写这个; gcc 不能为你做这件事,Make 也不能为你做这件事。并非项目中的每个开发人员都需要知道如何构建该列表,只有编写 makefile 的该部分的开发人员才需要知道。所有其他文件都将使用该 makefile,并且添加新依赖项(例如 inner_class_3)的开发人员可以将其添加到列表中。

如果您的 makefile 在火灾中丢失,并且唯一知道所有依赖项的开发人员被公共汽车击中,那么重建列表实际上并不难:当您尝试使可执行时,链接器抱怨 foo::bar() 未定义,您 grep 并发现 foo::bar() 是在 inner_class_2.cpp 中定义的>,将 inner_class_2.o 添加到列表中。重复直到链接器停止抱怨。

PS一旦按顺序排列,您就可以大大简化 makefile 的其余部分:

%.o: %.cpp %.h
    gcc -c 
lt; -o $@

inner_class_1.o inner_class_2.o : inner_definitions.h

outer_class.o : inner_class_1.h inner_class_2.h

executable.o : outer_class.h

编辑:

  1. 我建议的方法需要列出可以制作的每个目标文件,仅列出构建“可执行文件”实际需要的文件;我从你的问题中推断出了这个清单。
  2. 将额外的目标文件传递给链接器对最终的可执行文件没有影响,但它确实会导致不必要的重建。例如,假设您将“alien.o”添加到“OBJ_FILES”。然后,如果您修改“alien.cpp”并运行“makeexecutable”,它将重建“alien.o”和“executable”,即使实际上没有必要这样做。 更正(感谢slowdog):不必要的目标文件作为死代码进入最终的可执行文件 - 但我仍然对不必要的重建是正确的。
  3. 将目标文件组织到档案和共享库通常很方便,但并没有真正改变任何东西。
  4. 我知道没有可靠的方法来自动构造对象列表 - 也就是说,一种可以处理问题情况的方法,例如在两个不同的源文件中定义相同的函数时。如果涉及单元测试,这可能会成为一个真正的问题。 但是您可以在 makefile 中完成它如果您遵循简单的命名约定。
  5. 在 makefile 中执行此操作的技巧是相当高级的。老实说,我认为您最好以简单的方式执行此操作,直到您对这些工具更加熟悉为止。

编辑:
好吧,这是先进技术的概述。

首先,考虑所有#included 头文件。最好让 Make 处理依赖项,而不是像上面的 makefile 那样手动放入它们。这是一个简单的任务:如果 X.cpp #includes Yh (直接或通过一些 #included 头文件链),那么 Xo 将依赖于 Yh 这已经被计算为 "高级自动依赖生成"。但是如果您遵循严格的命名约定,则可以更进一步:如果在 Xh 中声明但未定义的所有内容都在 X.cpp 中定义,那么通过遵循相同的 #include 语句树,我们应该能够构造一个列表所需的目标文件,这些文件将成为可执行文件的依赖项。

这确实需要一次性吸收很多内容,所以我不会尝试通过一个示例来完成。我建议您查看该文档,看看它如何生成 Yh 依赖项,然后尝试将其应用到示例 makefile,然后考虑“进一步”应该做什么。

稍后您可以将其应用到测试工具,其中的目标文件为,例如 outer_class.ostub_inner_class_1.ostub_inner_class_2.otest_outer_class.o

Your makefile must have a list of objects to link together, like so:

OBJ_FILES = inner_definitions.o inner_class_1.o inner_class_2.o \ 
outer_class.o executable.o

executable : $(OBJ_FILES)
    gcc $^ -o $@

Someone must write this; gcc can't do it for you, Make can't do it for you. Not every developer on the project needs to know how to construct that list, only the one who writes that part of the makefile. All the others will use that makefile, and a developer who adds a new dependency (e.g. inner_class_3) can add it to the list.

And if your makefile is lost in a fire and the only developer who knows all the dependencies is hit by a bus, it really isn't hard to reconstruct the list: when you try to make executable, the linker complains that foo::bar() is undefined, you grep around and discover that foo::bar() is defined in inner_class_2.cpp, you add inner_class_2.o to the list. Repeat until the linker stops complaining.

P.S. Once that's in order, you can simplify the rest of the makefile quite a lot:

%.o: %.cpp %.h
    gcc -c 
lt; -o $@

inner_class_1.o inner_class_2.o : inner_definitions.h

outer_class.o : inner_class_1.h inner_class_2.h

executable.o : outer_class.h

EDIT:

  1. The method I suggested does not require listing every object file that can be made, just the ones that are actually needed to build `executable`; I inferred the list from your question.
  2. Passing extra object files to the linker makes no difference to the final executable, but it does lead to unnecessary rebuilding. For example, suppose you add `alien.o` to `OBJ_FILES`. Then if you modify `alien.cpp` and run `make executable`, it will rebuild `alien.o` and `executable` even though there's no real need to do so. Correction (thanks to slowdog): unnecessary object files go into the final executable as dead code-- but I'm still right about unnecessary rebuilding.
  3. Organizing object files into archives and shared libraries is often convenient, but doesn't really change anything here.
  4. I know of no robust way to automatically construct the object list -- that is, a way that could deal with problem cases such as when the same function is defined in two different source files. This could become a real problem if unit tests are involved. But you could do it within your makefile if you follow a simple naming convention.
  5. The trick for doing it within your makefile is a pretty advanced one. I honestly think you'd be better off doing this the simple way until you're more comfortable with the tools.

EDIT:
All right, here's an outline of the advanced technique.

First, consider all of the #included header files. It would be nice to have Make handle the dependencies instead of putting them in by hand, as in the makefile above. And this is a straightforward task: if X.cpp #includes Y.h (either directly or through some chain of #included header files), then X.o will depend on Y.h. This has already been worked out as "Advanced Auto-Dependency Generation". But if you follow a strict naming convention, you can take it a step further: if everything declared but not defines in X.h is defined in X.cpp, then by following the same tree of #include statements we should be able to construct a list of the needed object files, which will then be the dependencies of executable.

This really is a lot to absorb at once, so I won't try to work through an example. I suggest you look over the document and see how it can generate the Y.h dependencies, then try applying it to the example makefile, then think about what the "step further" should do.

Later you can apply it to the test harness, where the object files are, e.g., outer_class.o, stub_inner_class_1.o, stub_inner_class_2.o and test_outer_class.o.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文