.o文件和.a文件的区别

77

这两种文件类型有什么区别?我发现我的 C++ 应用在构建可执行文件时链接了这两种类型。

如何构建 .a 文件?希望能提供链接、参考资料以及特别是示例。

6个回答

78

.o文件是目标文件。它们是编译器的输出,也是链接器/库工具的输入。

.a文件是归档文件。它们是一组目标文件或静态库,也是链接器的输入之一。

附加内容

我没有注意到您问题中的 "examples" 部分。通常,您将使用makefile来生成静态库。

AR = ar 
CC = gcc

objects := hello.o world.o

libby.a: $(objects)
    $(AR) rcu $@ $(objects)

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

这将会编译hello.cworld.c成为对象,然后将它们打包成库。根据平台的不同,您可能还需要运行一个称为ranlib的实用程序,在存档中生成目录表。

有趣的是:.a文件在技术上是存档文件而不是库文件。它们类似于没有压缩的zip文件,尽管它们使用一个更古老的文件格式。由像ranlib这样的工具生成的目录表是使存档成为的关键。Java归档文件(.jar)也类似,它们是由Java归档器创建的带有一些特殊目录结构的zip文件。


1
这是关于Make的主题,但我想补充一下,您还可以在libby.a目标的配方行($(AR) rcu $@ $(objects))中使用$<$^来引用第一个或所有前置目标。这将产生相同的效果,但也消除了指定$(objects)两次的冗余,如果变量名称或前提条件发生更改,则需要在两个位置更新引用,并且代码量较少。 - Shammel Lee
2
@ShammelLee - 我通常避免使用 $^,因为它是GNU Make的扩展,在BSD makePOSIX make中不存在。虽然大多数Linux发行版都非常基于GNU,但从bash到dash的/bin/sh切换足以让我继续避免使用GNU特定的扩展。 - D.Shawley
@D.Shawley,"图书管理员"是什么意思?此外,您是否在说我们可以像使用“.tar”一样使用“.a”? - Pacerier
1
@Pacerier - 通常图书管理员会向存档添加元数据,使其成为特定编译工具链的“库”。至于使用.a文件作为存档,是的,您可以完全这样做。那就是它们被制作的目的。 tar格式比旧的ar格式包含更多的文件元数据。 - D.Shawley

26

.o 文件是编译单个编译单元(基本上是一个源代码文件,带有相关的头文件)的结果,而 .a 文件则是一个或多个 .o 文件打包成的库。


13

D Shawley的回答很好,我想要补充一些观点,因为其他回答反映了对正在发生的事情的不完整理解。

请记住,归档文件(.a)不仅限于包含目标文件(.o)。它们可以包含任意文件。虽然很少有用,但参见动态链接器依赖项信息嵌入档案以了解愚蠢的链接器技巧。

还要注意,目标文件(.o)不一定是单个编译单元的结果。可以将几个较小的对象文件部分链接成单个较大的文件。

http://www.mihaiu.name/2002/library_development_linux/ - 在此页面中搜索“partial”


12
链接.a和链接.o的另一个方面是:在链接时,所有作为参数传递的.o文件都包含在最终的可执行文件中,而任何.a参数的条目仅在程序解析符号依赖关系时才包含在链接器输出中。
更具体地说,每个.a文件都是由多个.o文件组成的存档。您可以认为每个.o都是代码的原子单位。如果链接器需要从这些单元之一获取符号,则整个单元将被吸入最终二进制文件中;但除非它们也被使用,否则不会吸入其他任何单元。
相比之下,当您在命令行上传递一个.o文件时,链接器会吸入它,因为您请求了它。
为了说明这一点,请考虑以下示例,其中我们有一个静态库,包含两个对象a.ob.o。我们的程序只引用a.o中的符号。我们将比较链接一起传递a.ob.o与包含相同两个对象的静态库时,链接器的处理方式。
// header.hh
#pragma once

void say_hello_a();
void say_hello_b();

// a.cc
#include "header.hh"
#include <iostream>

char hello_a[] = "hello from a";

void say_hello_a()
{
        std::cout << hello_a << '\n';
}

// b.cc
#include "header.hh"
#include <iostream>

char hello_b[] = "hello from b";

void say_hello_b()
{
        std::cout << hello_b << '\n';
}

// main.cc
#include "header.hh"

int main()
{
        say_hello_a();
}

我们可以使用这个Makefile编译代码:

.PHONY = compile archive link all clean

all: link

compile:
        @echo ">>> Compiling..."
        g++ -c a.cc b.cc main.cc

archive: compile
        @echo ">>> Archiving..."
        ar crs lib.a a.o b.o

link: archive
        @echo ">>> Linking..."
        g++ -o main_o main.o a.o b.o
        g++ -o main_a main.o lib.a

clean:
        rm *.o *.a main_a main_o

并获得两个可执行文件main_omain_a,它们的不同之处在于在第一种情况下,a.ccb.cc的内容是通过两个.o文件提供的,而在第二种情况下则是通过一个.a文件。

最后,我们使用nm工具检查最终可执行文件的符号:

$ nm --demangle main_o | grep hello
00000000000011e9 t _GLOBAL__sub_I_hello_a
000000000000126e t _GLOBAL__sub_I_hello_b
0000000000004048 D hello_a
0000000000004058 D hello_b
0000000000001179 T say_hello_a()
00000000000011fe T say_hello_b()

$ nm --demangle main_a | grep hello
00000000000011e9 t _GLOBAL__sub_I_hello_a
0000000000004048 D hello_a
0000000000001179 T say_hello_a()

注意观察,main_a 实际上缺少来自 b.o 的不必要符号。也就是说,链接器没有将存档文件 lib.a 中的 b.o 内容引入,因为没有使用 b.cc 中的任何符号。


2
这应该是被接受的答案。写得很好,易于理解,并且有很棒的例子! - leosh

7
您可以使用ar.o文件(目标文件)创建.a文件(静态库)。
有关详细信息,请参见man ar

3
我认为 .a 文件是一个存储多个目标文件的归档文件。

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