第十七部分(编译程序)

2020-04-10

什么是编译?

简而言之,编译就是把源码(一个由程序员编写的人类可读的程序的说明)翻译成计算机处理器的语言的过程。

所有的程序都是可编译的吗?

不是。正如我们所看到的,有些程序比如 shell 脚本就不需要编译。它们直接执行。这些程序是用所谓的脚本或解释型语言编写的。近年来,这些语言变得越来越流行,包括 Perl、Python、PHP、Ruby 和许多其它语言。

脚本语言由一个叫做解释器的特殊程序执行。一个解释器输入程序文件,读取并执行程序中包含的每一条指令。通常来说,解释型程序执行起来要比编译程序慢很多。这是因为每次解释型程序执行时,程序中每一条源码指令都需要翻译,而一个已经编译好的程序,一条源码指令只翻译了一次,翻译后的指令会永久地记录到最终的执行文件中。

那么为什么解释型程序这样流行呢?对于许多编程任务来说,原因是“足够快”,但是真正的优势是一般来说开发解释型程序要比编译程序快速且容易。通常程序开发需要经历一个不断重复的写码、编译和测试周期。随着程序变得越来越大,编译阶段会变得相当耗时。解释型语言删除了编译步骤,这样就加快了程序开发。

编译一个 C 语言

在我们编译之前,然而我们需要一些工具,像编译器、链接器以及make。在 Linux 环境中,普遍使用的 C 编译器叫做 gcc(GNU C 编译器),最初由 Richard Stallman 写出来的。大多数 Linux 系统发行版默认不安装 gcc。我们可以这样查看该编译器是否存在:

[root@VM_0_7_centos ~]# which gcc
/usr/bin/gcc

在这个例子中的输出结果表明安装了 gcc 编译器。

得到源码

为了我们的编译练习,我们将编译一个叫做 diction 的程序,来自 GNU 项目。这是一个小巧方便的程序,检查文本文件的书写质量和样式。就程序而言,它相当小,且容易创建。
遵照惯例,首先我们要创建一个名为 src 的目录来存放我们的源码,然后使用 ftp 协议把源码下载下来。

[root@VM_0_7_centos ~]# mkdir src
[root@VM_0_7_centos ~]# cd src/
[root@VM_0_7_centos src]# ftp ftp.gnu.org
Trying 209.51.188.20...
Connected to ftp.gnu.org (209.51.188.20).
220 GNU FTP server ready.
Name (ftp.gnu.org:root): anonymous
230-NOTICE (Updated October 13 2017):
230-
230-Because of security concerns with plaintext protocols, we still
230-intend to disable the FTP protocol for downloads on this server
230-(downloads would still be available over HTTP and HTTPS), but we
230-will not be doing it on November 1, 2017, as previously announced
230-here. We will be sharing our reasons and offering a chance to
230-comment on this issue soon; watch this space for details.
230-
230-If you maintain scripts used to access ftp.gnu.org over FTP,
230-we strongly encourage you to change them to use HTTPS instead.
230-
230----
230-
230-Due to U.S. Export Regulations, all cryptographic software on this
230-site is subject to the following legal notice:
230-
230-    This site includes publicly available encryption source code
230-    which, together with object code resulting from the compiling of
230-    publicly available source code, may be exported from the United
230-    States under License Exception "TSU" pursuant to 15 C.F.R. Section
230-    740.13(e).
230-
230-This legal notice applies to cryptographic software only. Please see
230-the Bureau of Industry and Security (www.bxa.doc.gov) for more
230-information about current U.S. regulations.
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> cd gnu/diction
250 Directory successfully changed.
ftp> sl
?Invalid command
ftp> ls
227 Entering Passive Mode (209,51,188,20,113,59).
150 Here comes the directory listing.
-rw-r--r--    1 3003     65534       68940 Aug 28  1998 diction-0.7.tar.gz
-rw-r--r--    1 3003     65534       90957 Mar 04  2002 diction-1.02.tar.gz
-rw-r--r--    1 3003     65534      141062 Sep 17  2007 diction-1.11.tar.gz
-rw-r--r--    1 3003     65534         189 Sep 17  2007 diction-1.11.tar.gz.sig
226 Directory send OK.
ftp> get diction-1.11.tar.gz
local: diction-1.11.tar.gz remote: diction-1.11.tar.gz
227 Entering Passive Mode (209,51,188,20,84,144).
150 Opening BINARY mode data connection for diction-1.11.tar.gz (141062 bytes).
226 Transfer complete.
141062 bytes received in 15.1 secs (9.32 Kbytes/sec)
ftp> bye
221 Goodbye.
[root@VM_0_7_centos src]# ls
diction-1.11.tar.gz

一旦 tar 文件下载下来之后,必须解包。通过 tar 程序可以完成:

[root@VM_0_7_centos src]# tar xzf diction-1.11.tar.gz 
[root@VM_0_7_centos src]# ls
diction-1.11  diction-1.11.tar.gz

该 diction 程序,像所有的 GNU 项目软件,遵循着一定的源码打包标准。其它大多数在 Linux 生态系统中可用的源码也遵循这个标准。该标准的一个条目是,当源码 tar 文件打开的时候,会创建一个目录,该目录包含了源码树,并且这个目录将会命名为 project-x.xx,其包含了项目名称和它的版本号两项内容。这种方案能在系统中方便安装同一程序的多个版本。然而,通常在打开 tarball 之前检验源码树的布局是个不错的主意。一些项目不会创建该目录,反而,会把文件直接传递给当前目录。这会把你的(除非组织良好的)src 目录弄得一片狼藉。为了避免这个,使用下面的命令,检查 tar 文件的内容:

tar tzvf tarfile | head ---

检查源码树

打开该 tar 文件,会创建一个新的目录,名为 diction-1.11。这个目录包含了源码树。让我们看一下里面的内容:

[root@VM_0_7_centos src]# cd diction-1.11/
[root@VM_0_7_centos diction-1.11]# ls
config.guess  configure     de            diction.c     diction.spec.in  en_GB      getopt.c      INSTALL      misc.c  nl      sentence.c  style.c
config.h.in   configure.in  de.po         diction.pot   diction.texi.in  en_GB.po   getopt.h      install-sh   misc.h  nl.po   sentence.h  test
config.sub    COPYING       diction.1.in  diction.spec  en               getopt1.c  getopt_int.h  Makefile.in  NEWS    README  style.1.in

这些文件包含了程序描述,如何建立和安装它的信息,还有其它许可条款。在试图建立程序之前,仔细阅读 README 和 INSTALL 文件,总是一个不错的主意。

在这个目录中,其它有趣的文件是那些以.c.h 为后缀的文件,这些.c 文件包含了由该软件包提供的两个 C 程序(style 和 diction),被分割成模块。这是一种常见做法,把大型程序分解成更小,更容易管理的代码块。源码文件都是普通文本,可以用 less 命令查看。这些.h 文件被称为头文件。它们也是普通文件。头文件包含了程序的描述,这些程序被包括在源码文件或库中。为了让编译器链接到模块,编译器必须接受所需的所有模块的描述,来完成整个程序。

构建程序

大多数程序通过一个简单的,两个命令的序列构建:

./configure
make

这个 configure 程序是一个 shell 脚本,由源码树提供。它的工作是分析程序构建环境。大多数源码会设计为可移植的。也就是说,它被设计成能够在不止一种类 Unix 系统中进行构建。但是为了做到这一点,在建立程序期间,为了适应系统之间的差异,源码可能需要经过轻微的调整。configure 也会检查是否安装了必要的外部工具和组件。让我们运行 configure 命令。因为 configure 命令所在的位置不是位于 shell 通常期望程序所呆的地方,我们必须明确地告诉shell 它的位置,通过在命令之前加上./ 字符,来表明程序位于当前工作目录:

[root@VM_0_7_centos diction-1.11]# ./configure 

configure 将会输出许多信息,随着它测试和配置整个构建过程。当结束后,输出结果看起来像这样:

checking build system type... x86_64-unknown-linux-gnu
checking host system type... x86_64-unknown-linux-gnu
checking for gcc... gcc
checking for C compiler default output file name... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables... 
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking for a BSD-compatible install... /usr/bin/install -c
checking for strerror... yes
checking for library containing regcomp... none required
checking for broken realloc... no
checking for msgfmt... yes
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking libintl.h usability... yes
checking libintl.h presence... yes
checking for libintl.h... yes
checking for library containing gettext... none required
configure: creating ./config.status
config.status: creating Makefile
config.status: creating diction.1
config.status: creating diction.texi
config.status: creating diction.spec
config.status: creating style.1
config.status: creating test/rundiction
config.status: creating config.h

这里最重要的事情是没有错误信息。如果有错误信息,整个配置过程失败,然后程序不能构建直到修正了错误。

我们看到在我们的源码目录中 configure 命令创建了几个新文件。最重要一个是 Makefile。Makefile 是一个配置文件,指示 make 程序究竟如何构建程序。没有它,make 程序就不能运行。Makefile 是一个普通文本文件,所以我们能查看它。

这个 make 程序把一个 makefile 文件作为输入(通常命名为 Makefile),makefile 文件描述了包括最终完成的程序的各组件之间的关系和依赖性。

让我们运行 make 命令并构建我们的程序:

[root@VM_0_7_centos diction-1.11]# make

这个 make 程序将会运行,使用 Makefile 文件的内容来指导它的行为。它会产生很多信息。
当 make 程序运行结束后,现在我们将看到所有的目标文件出现在我们的目录中。

[root@VM_0_7_centos diction-1.11]# ls
config.guess   config.sub    de.mo         diction.c        diction.texi     en_GB.po   getopt_int.h  Makefile.in  nl          sentence.h  style.c
config.h       configure     de.po         diction.o        diction.texi.in  getopt1.c  getopt.o      misc.c       nl.mo       sentence.o  style.o
config.h.in    configure.in  diction       diction.pot      en               getopt1.o  INSTALL       misc.h       nl.po       style       test
config.log     COPYING       diction.1     diction.spec     en_GB            getopt.c   install-sh    misc.o       README      style.1
config.status  de            diction.1.in  diction.spec.in  en_GB.mo         getopt.h   Makefile      NEWS         sentence.c  style.1.in

在这些文件之中,我们看到 diction 和 style,我们开始要构建的程序。恭喜一切正常!我们刚才源码编译了我们的第一个程序。但是出于好奇,让我们再运行一次 make 程序:

[root@VM_0_7_centos diction-1.11]# make
make: Nothing to be done for `all'.

它只是产生这样一条奇怪的信息。怎么了?为什么它没有重新构建程序呢?啊,这就是make 奇妙之处了。make 只是构建需要构建的部分,而不是简单地重新构建所有的内容。由于所有的目标文件都存在,make 确定没有任何事情需要做。我们可以证明这一点,通过删除一个目标文件,然后再次运行 make 程序,看看它做些什么。让我们去掉一个中间目标文件:

[root@VM_0_7_centos diction-1.11]# rm getopt.o
rm: remove regular file ‘getopt.o’? y
[root@VM_0_7_centos diction-1.11]# ls
config.guess   config.sub    de.mo         diction.c        diction.texi     en_GB.po   getopt_int.h  misc.c  nl.mo       sentence.o  style.o
config.h       configure     de.po         diction.o        diction.texi.in  getopt1.c  INSTALL       misc.h  nl.po       style       test
config.h.in    configure.in  diction       diction.pot      en               getopt1.o  install-sh    misc.o  README      style.1
config.log     COPYING       diction.1     diction.spec     en_GB            getopt.c   Makefile      NEWS    sentence.c  style.1.in
config.status  de            diction.1.in  diction.spec.in  en_GB.mo         getopt.h   Makefile.in   nl      sentence.h  style.c
[root@VM_0_7_centos diction-1.11]# make
gcc -c -I. -DSHAREDIR=\"/usr/local/share\" -DLOCALEDIR=\"/usr/local/share/locale\" -g -O2 -pipe -Wno-unused -Wshadow -Wbad-function-cast -Wmissing-prototypes -Wstrict-prototypes -Wcast-align -Wcast-qual -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-declarations -Wnested-externs -Wundef -pedantic -fno-common getopt.c
gcc -o diction -g diction.o sentence.o misc.o \
	getopt.o getopt1.o 
gcc -o style -g style.o sentence.o misc.o \
	getopt.o getopt1.o -lm 

我们看到 make 重新构建了 getopt.o 文件,并重新链接了 diction 和 style 程序,因为它们依赖于丢失的模块。这种行为也指出了 make 程序的另一个重要特征:它保持目标文件是最新的。make 坚持目标文件要新于它们的依赖文件。

安装程序

打包良好的源码经常包括一个特别的 make 目标文件,叫做 install。这个目标文件将在系统目录中安装最终的产品,以供使用。通常,这个目录是 /usr/local/bin,为在本地所构建软件的传统安装位置。然而,通常普通用户不能写入该目录,所以我们必须变成超级用户,来执行安装操作:

[root@VM_0_7_centos diction-1.11]# sudo make install

执行了安装后,我们可以检查下程序是否已经可用:

[root@VM_0_7_centos diction-1.11]# which diction 
/usr/local/bin/diction
[root@VM_0_7_centos diction-1.11]# man diction

总结

在这一部分中,我们已经知道了三个简单命令:

./configure
make
make install

可以用来构建许多源码包。我们也知道了在程序维护过程中,make 程序起到了举足轻重的作用。make 程序可以用到任何需要维护一个目标/依赖关系的任务中,不仅仅为了编译源代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GNU libiconv-1.15和libintl-0.19.8.1,VS2015工程和 Makefile 命令行编译文件 CSDN-tags: libiconv1.15 libintl 0.19.8.1 VS2015 Makefile 声明: 代码为本人良心制作,虽然花费了一定的时间和精力,但不保证完全没有错误。如果您下载并使用了本代码,将其包含在您发布的应用中,给您带来了经济上,心理上,生活上的损失,本人不负有责任。 所有代码都基于官网进行修改,iconv的代码来源于libiconv-1.15的lib目录,intl的代码来源于gettext-runtime的intl目录。 intl需要iconv的支持,如果想单独编译不需要iconv的intl, 请自行修改intl目录下的config.h文件, /* Define if you have the iconv() function and it works. */ #define HAVE_ICONV 1 /* Define to 1 if you have the header file. */ #define HAVE_ICONV_H 1 为: /* Define if you have the iconv() function and it works. */ /* #undef HAVE_ICONV */ /* Define to 1 if you have the header file. */ /* #undef HAVE_ICONV_H */ 并修改VS工程中预处理器定义,删掉/DDEPENDS_ON_LIBICONV=1 VS的工程为VS2015的工程文件,使用VC140工具集,如果没有2015,也可以用Makefile.mak文件来编译。 nmake /f Makefile.mak [DLL] [DEBUG] 指定 DLL=1 编译dll版本,默认是lib版本,libiconv.lib , libintl.lib。 指定 DEBUG=1 编译debug版本,默认是release版本。 同理,如果intl不需要iconv,请修改上面的配置文件选项,并, 删掉Makefile.mak中/DDEPENDS_ON_LIBICONV=1 配置文件的制作,参考了以下几处,修改的大概原则是,能定义的全定义了,就这样: https://github.com/kahrl/gettext-msvc 这个地址提供了libiconv和libintl的VS工程和配置文件。 https://github.com/winlibs 为编译PHP而修改的libiconv和libintl源码,libiconv是最新1.15版本,gettext不是。 附带的Cygwin目录中的工具 libiconv官方的制作文档,用cygwin配合VC,生成的配置文件。 一大堆的宏和函数搞得我头晕脑涨。所以配置文件如果有错误,也在所难免,所有修改的文件,如config.h,iconv.h,libintl.h都附带了原始文件。 如: 配置文件 原始文件 DLL原始文件 LIB原始文件 config.h config.h.in iconv.h iconv.h.build.in iconv.h.in localcharset.h localcharset.h.build.in localcharset.h.in libgnuintl.h libgnuintl.in.h 欢迎同学们下载测试。有问题可以给我发邮件travel981cn@139.com
GNU libiconv-1.15和libintl-0.19.8.1,VS2017工程和 Makefile.mak 命令行编译文件 CSDN-tags: libiconv1.15 libintl-0.19.8.1 VS2017 SDK17134.12 声明: 最新版,修正了relocatable.c文件中GetModuleFileName函数的问题。在ANSI下,等同于:GetModuleFileNameA;Unicode下等同于:GetModuleFileNameW,如果你的程序出错,请检查。 代码为本人良心制作,虽然花费了一定的时间和精力,但不保证完全没有错误。如果您下载并使用了本代码,将其包含在您发布的应用中,给您带来了经济上,心理上,生活上的损失,本人不负有责任。 所有代码都基于官网进行修改,iconv的代码来源于libiconv-1.15的lib目录,intl的代码来源于gettext-runtime的intl目录。 intl需要iconv的支持,如果想单独编译不需要iconv的intl, 请自行修改intl目录下的config.h文件, /* Define if you have the iconv() function and it works. */ #define HAVE_ICONV 1 /* Define to 1 if you have the header file. */ #define HAVE_ICONV_H 1 为: /* Define if you have the iconv() function and it works. */ /* #undef HAVE_ICONV */ /* Define to 1 if you have the header file. */ /* #undef HAVE_ICONV_H */ 并修改VS工程中预处理器定义,删掉/DDEPENDS_ON_LIBICONV=1 VS的工程为VS2017的工程文件,使用VC141工具集,SDK10.0.17134.12,如果没有2017,也可以用Makefile.mak文件来编译。 或者你自己降低版本,我最初使用的最低版本是VC140_xp,SDK7.1A,也就是VS2015默认安装的兼容xp工具集,VS2015以下版本未测试。 nmake /f Makefile.mak [DLL] [DEBUG] 指定 DLL=1 编译dll版本,默认是lib版本,libiconv.lib , libintl.lib。 指定 DEBUG=1 编译debug版本,默认是release版本。 同理,如果intl不需要iconv,请修改上面的配置文件选项,并, 删掉Makefile.mak中/DDEPENDS_ON_LIBICONV=1 配置文件的制作,参考了以下几处,修改的大概原则是,能定义的全定义了,就这样: https://github.com/kahrl/gettext-msvc 这个地址提供了libiconv和libintl的VS工程和配置文件。 https://github.com/winlibs 为编译PHP而修改的libiconv和libintl源码。 附带的Cygwin目录中的工具 libiconv官方的制作文档,用cygwin配合VC,生成的配置文件。 一大堆的宏和函数搞得我头晕脑涨。所以配置文件如果有错误,也在所难免,所有修改的文件,如config.h,iconv.h,libintl.h都附带了原始文件。 如: 配置文件 原始文件 DLL原始文件 LIB原始文件 config.h config.h.in iconv.h iconv.h.build.in iconv.h.in localcharset.h localcharset.h.build.in localcharset.h.in libintl.h libgnuintl.in.h 欢迎同学们下载测试。有问题可以给我发邮件travel981cn@139.com
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值