如何使用 Linux 的 ar 命令创建静态库

在软件开发过程中,Linux的ar命令常被用来构建函数库。本教程将引导你了解如何创建、修改和在程序中使用静态库,并附有实际代码示例。

ar命令历史悠久,自1971年就已经存在。其名称ar来源于该工具最初的用途——创建归档文件。归档文件可以被视为一个容器,用于存放其他文件,有时是大量文件。文件可以在归档文件中添加、删除或从中提取。如今,人们不再依赖ar来完成这些功能,这项工作已被tar等其他实用工具所取代。

然而,ar命令在某些特定场景下仍然非常有用。它主要用于创建静态库,这些静态库在软件开发中扮演着重要角色。此外,ar还用于创建软件包文件,例如Debian Linux及其衍生版本(如Ubuntu)中使用的.deb文件。

本文将详细介绍创建和修改静态库的步骤,并演示如何在程序中使用它。为了更好地说明,我们将创建一个静态库,其主要功能是对文本字符串进行加密和解密。

请注意,这里提供的加密方法仅用于演示目的,请勿将其用于任何有价值的敏感数据。这是一种非常简单的替换密码,它将A变为B,B变为C,以此类推。

cipher_encode()cipher_decode() 函数

我们将所有的操作都放在一个名为library的目录中,并在其中创建一个名为test的子目录。

library目录下,我们有两个文件。第一个文件名为cipher_encode.c,其中包含了cipher_encode()函数:

void cipher_encode(char *text)
{
  for (int i=0; text[i] != 0x0; i++) {
    text[i]++;
  }

} // end of cipher_encode

第二个文件名为cipher_decode.c,其中包含了对应的cipher_decode()函数:

void cipher_decode(char *text)
{
  for (int i=0; text[i] != 0x0; i++) {
    text[i]--;
  }

} // end of cipher_decode

这些包含程序指令的文件被称为源代码文件。我们将创建一个名为libcipher.a的库文件,它将包含这两个源代码文件的编译版本。此外,我们还会创建一个名为libcipher.h的头文件,其中包含了我们新库中两个函数的定义。

有了库文件和头文件,开发者就可以在自己的程序中使用这两个函数。他们无需重新编写这些功能,只需简单地利用我们提供的库即可。

编译 cipher_encode.ccipher_decode.c 文件

要编译源代码文件,我们将使用gcc,这是标准的GNU编译器-c选项(编译但不链接)告诉gcc编译文件后停止。它会从每个源代码文件生成一个中间文件,称为目标文件。通常,gcc链接器会获取所有目标文件并将它们链接在一起以生成可执行程序。但我们使用-c选项跳过了这一步,我们只需要目标文件。

首先,让我们检查一下是否拥有了我们所需要的文件。

ls -l

确认该目录下存在两个源代码文件。接下来,我们将使用gcc将它们编译成目标文件。

gcc -c cipher_encode.c
gcc -c cipher_decode.c

如果一切顺利,gcc应该不会输出任何信息。

这将生成两个与源代码文件同名但扩展名为.o的目标文件。这些就是我们需要添加到库文件中的文件。

ls -l

创建 libcipher.a

要创建库文件(本质上是一个归档文件),我们将使用ar命令。

我们使用-c选项(创建)来创建库文件,-r选项(添加或替换)将文件添加到库中,以及-s选项(索引)来创建库文件的内部索引。

我们将库文件命名为libcipher.a。 在命令行中指定该名称,以及要添加到库中的目标文件的名称。

ar -crs libcipher.a cipher_encode.o cipher_decode.o

如果列出目录中的文件,我们将会看到现在多了一个libcipher.a文件。

ls -l

我们可以使用ar命令的-t选项(列出)来查看库文件中的模块。

ar -t libcipher.a

创建 libcipher.h 头文件

libcipher.h文件将被包含在任何使用libcipher.a库的程序中。libcipher.h文件必须包含库中函数的定义。

要创建头文件,我们需要使用文本编辑器(例如gedit)输入函数定义。 将文件命名为“libcipher.h”并将其保存在与libcipher.a文件相同的目录中。

void cipher_encode(char *text);
void cipher_decode(char *text);

使用 libcipher

验证新库的可靠方法是编写一个小型测试程序来使用它。 首先,创建一个名为test的目录。

mkdir test

将库文件和头文件复制到新目录中。

cp libcipher.* ./test

切换到新目录。

cd test

检查这两个文件是否已经复制到这里。

ls -l

我们需要创建一个可以使用该库的小程序,并证明它能正常工作。 在编辑器中输入以下代码,并将内容保存到测试目录中名为“test.c”的文件中。

#include <stdio.h>
#include <stdlib.h>

#include "libcipher.h"

int main(int argc, char *argv[])
{
  char text[]="How-To Geek loves Linux";

  puts(text);

  cipher_encode(text);
  puts(text);

  cipher_decode(text);
  puts(text);

  exit (0);

} // end of main

程序的逻辑很简单:

  • 它包含了libcipher.h文件,以便可以访问库中函数的定义。
  • 创建了一个名为text的字符串,并初始化为“How-To Geek loves Linux”。
  • 将该字符串打印到屏幕上。
  • 调用cipher_encode()函数对字符串进行编码,并将编码后的字符串打印到屏幕上。
  • 调用cipher_decode()函数对字符串进行解码,并将解码后的字符串打印到屏幕上。

要生成测试程序,我们需要编译test.c程序并将其链接到库。-o选项(输出)指示gcc生成的可执行程序的名称。

gcc test.c libcipher.a -o test

如果gcc没有输出任何信息,并直接返回到命令提示符,那么一切正常。现在来测试我们的程序,关键时刻到了:

./test

我们可以看到预期输出。 测试程序打印出纯文本,加密后的文本,以及解密后的文本。它正在使用我们新库中的函数。我们的库运行良好!

成功了! 但为什么停在这里呢?

向库中添加另一个模块

让我们向库中添加另一个函数。 我们将添加一个函数,开发者可以使用它来显示他们正在使用的库的版本信息。 我们需要创建新函数、编译它,并将新的目标文件添加到现有的库文件中。

在编辑器中键入以下行。 将编辑器的内容保存到库目录中名为cipher_version.c的文件中。

#include <stdio.h>

void cipher_version(void)
{
  puts("How-To Geek :: VERY INSECURE Cipher Library");
  puts("Version 0.0.1 Alphan");

} // end of cipher_version

我们需要将新函数的定义添加到libcipher.h头文件中。 在该文件的底部添加一行,使其看起来像这样:

void cipher_encode(char *text);
void cipher_decode(char *text);
void cipher_version(void);

保存修改后的libcipher.h文件。

我们需要编译cipher_version.c文件,以便获得cipher_version.o目标文件。

gcc -c cipher_version.c

这将创建一个cipher_version.o文件。 我们可以使用以下命令将新目标文件添加到libcipher.a库中。 -v选项(详细)使通常保持沉默的ar命令输出执行的操作信息。

ar -rsv libcipher.a cipher_version.o

新的目标文件被添加到库文件中。ar打印出确认。“a”表示“添加”。

我们可以使用-t选项(列出)来查看库文件中有哪些模块。

ar -t libcipher.a

现在我们的库文件中有三个模块了。 让我们使用新功能。

使用 cipher_version() 函数

首先,从测试目录中删除旧的库和头文件,复制新文件,然后切换回测试目录。

删除旧版本的文件。

rm ./test/libcipher.*

将新版本复制到测试目录中。

cp libcipher.* ./test

进入测试目录。

cd test

现在我们可以修改test.c程序,使其使用新的库函数。

我们需要在调用cipher_version()函数的test.c程序中添加一行,我们将其放在第一个puts(text)之前。

#include <stdio.h>
#include <stdlib.h>

#include "libcipher.h"

int main(int argc, char *argv[])
{
  char text[]="How-To Geek loves Linux";

  // new line added here
  cipher_version();

  puts(text);

  cipher_encode(text);
  puts(text);

  cipher_decode(text);
  puts(text);

  exit (0);

} // end of main

将此保存为test.c。现在编译并测试新功能是否可用。

gcc test.c libcipher.a -o test

现在运行新版本的测试程序:

新功能正在正常运行。 我们可以在测试输出的开头看到库的版本信息。

但可能存在问题。

替换库中的模块

这不是库的第一个版本,而是第二个。 我们的版本号不正确。 在第一个版本中没有cipher_version()函数,所以这个版本应该是“0.0.2”。我们需要用更正后的函数替换库中的cipher_version()函数。

幸运的是,ar可以轻松实现这一点。

首先,让我们编辑库目录中的cipher_version.c文件。 将“Version 0.0.1 Alpha”文本更改为“Version 0.0.2 Alpha”。 修改后应该如下所示:

#include <stdio.h>

void cipher_version(void)
{
  puts("How-To Geek :: VERY INSECURE Cipher Library");
  puts("Version 0.0.2 Alphan");

} // end of cipher_version

保存此文件。 我们需要再次编译它以创建新的cipher_version.o目标文件。

gcc -c cipher_version.c

现在,我们将用新编译的版本替换库中现有的cipher_version.o模块。

我们之前使用-r选项(添加或替换)来向库中添加新模块。 当我们将其与库中已存在的模块一起使用时,ar将用新版本替换旧版本。 -s选项(索引)将更新库索引,-v选项(详细)将使ar输出执行的操作信息。

ar -rsv libcipher.a cipher_version.o

这次,ar报告它已替换了cipher_version.o模块。“r”表示“替换”。

使用更新后的 cipher_version() 函数

我们应该使用修改后的库并检查它是否有效。

将库文件复制到测试目录。

cp libcipher.* ./test

进入测试目录。

cd ./test

我们需要使用新的库再次编译我们的测试程序。

gcc test.c libcipher.a -o test

现在可以测试我们的程序了。

./test

测试程序的输出符合我们的预期。版本字符串中显示了正确的版本号,并且加密和解密例程正在正常运行。

从库中删除模块

尽管看起来有些可惜,但我们还是从库文件中删除cipher_version.o文件。

为此,我们将使用-d选项(删除)。我们还将使用-v选项(详细),以便ar输出执行的操作信息。 我们还将包含-s选项(索引)来更新库文件中的索引。

ar -dsv libcipher.a cipher_version.o

ar报告它已经删除了该模块。“d”表示“删除”。

如果我们要求ar列出库文件中的模块,我们将看到又回到了两个模块。

ar -t libcipher.a

如果从库中删除模块,请记住从库的头文件中删除它们的定义。

分享你的代码

库以一种实用且私密的方式使代码可共享。 任何拥有库文件和头文件的人都可以使用您的库,但您的实际源代码仍然是私有的。