GNU C 编译器的程序员入门指南
编译 GCC
![](/data/attachment/album/202205/30/111925gbh7yldbolroheqy.jpg)



> 
> 带你一窥生成二进制文件步骤的幕后，以便在出现一些错误时，你知道如何逐步解决问题。
> 
> 
> 


C 语言广为人知，深受新老程序员的好评。使用 C 语言编写的源文件代码，使用了标准的英语术语，因而人们可以方便阅读。然而，计算机只能理解二进制代码。为将代码转换为机器语言，你需要使用一种被称为 <ruby> 编译器 <rt>  compiler </rt></ruby> 的工具。


最常见的编译器是 GCC（<ruby> GNU 编译器集 <rt>  GNU Compiler Collection </rt></ruby>）。编译过程涉及到一系列的中间步骤及相关工具。


### 安装 GCC


为验证在你的系统上是否已经安装了 GCC，使用 `gcc` 命令：



```
$ gcc --version

```

如有必要，使用你的软件包管理器来安装 GCC。在基于 Fedora 的系统上，使用 `dnf` ：



```
$ sudo dnf install gcc libgcc

```

在基于 Debian 的系统上，使用 `apt` ：



```
$ sudo apt install build-essential

```

在安装后，如果你想查看 GCC 的安装位置，那么使用：



```
$ whereis gcc

```

### 演示使用 GCC 来编译一个简单的 C 程序


这里有一个简单的 C 程序，用于演示如何使用 GCC 来编译。打开你最喜欢的文本编辑器，并在其中粘贴这段代码：



```
// hellogcc.c
#include <stdio.h>

int main() {
    printf("Hello, GCC!\n");
    return 0;
}

```

保存文件为 `hellogcc.c` ，接下来编译它：



```
$ ls
hellogcc.c

$ gcc hellogcc.c

$ ls -1
a.out
hellogcc.c

```

如你所见，`a.out` 是编译后默认生成的二进制文件。为查看你所新编译的应用程序的输出，只需要运行它，就像你运行任意本地二进制文件一样：



```
$ ./a.out
Hello, GCC!

```

### 命名输出的文件


文件名称 `a.out` 是非常莫名其妙的，所以，如果你想具体指定可执行文件的名称，你可以使用 `-o` 选项：


（LCTT 译注：注意这和最近 Linux 内核废弃的 a.out 格式无关，只是名字相同，这里生成的 a.out 是 ELF 格式的 —— 也不知道谁给起了个 `a.out` 这破名字，在我看来，默认输出文件名就应该是去掉了 `.c` 扩展名后的名字。by wxy）



```
$ gcc -o hellogcc hellogcc.c

$ ls
a.out hellogcc hellogcc.c

$ ./hellogcc
Hello, GCC!

```

当开发一个需要编译多个 C 源文件文件的大型应用程序时，这种选项是很有用的。


### 在 GCC 编译中的中间步骤


编译实际上有四个步骤，即使在简单的用例中 GCC 自动执行了这些步骤。


1. <ruby> 预处理 <rt>  Pre-Processing </rt></ruby>：GNU 的 C 预处理器（cpp）解析头文件（`#include` 语句），展开 <ruby> 宏 <rt>  macros </rt></ruby> 定义（`#define` 语句），并使用展开的源文件代码来生成一个中间文件，如 `hellogcc.i`。
2. <ruby> 编译 <rt>  Compilation </rt></ruby>：在这个期间中，编译器将预处理的源文件代码转换为指定 CPU 架构的汇编代码。由此生成是汇编文件使用一个 `.s` 扩展名来命名，如在这个示例中的 `hellogcc.s` 。
3. <ruby> 汇编 <rt>  Assembly </rt></ruby>：汇编程序（`as`）将汇编代码转换为目标机器代码，放在目标文件中，例如 `hellogcc.o` 。
4. <ruby> 链接 <rt>  Linking </rt></ruby>：链接器（`ld`）将目标代码和库代码链接起来生成一个可执行文件，例如 `hellogcc` 。


在运行 GCC 时，可以使用 `-v` 选项来查看每一步的细节：



```
$ gcc -v -o hellogcc hellogcc.c

```

![Compiler flowchart](/data/attachment/album/202205/30/111950jk2j507r2702jj7o.png)


### 手动编译代码


体验编译的每个步骤可能是很有用的，因此在一些情况下，你不需要 GCC 完成所有的步骤。


首先，除源文件文件以外，删除在当前文件夹下生成的文件。



```
$ rm a.out hellogcc.o

$ ls
hellogcc.c

```

#### 预处理器


首先，启动预处理器，将其输出重定向为 `hellogcc.i` ：



```
$ cpp hellogcc.c > hellogcc.i

$ ls
hellogcc.c hellogcc.i

```

查看输出文件，并注意一下预处理器是如何包含头文件和扩展宏中的源文件代码的。


#### 编译器


现在，你可以编译代码为汇编代码。使用 `-S` 选项来设置 GCC 只生成汇编代码：



```
$ gcc -S hellogcc.i

$ ls
hellogcc.c hellogcc.i hellogcc.s

$ cat hellogcc.s

```

查看汇编代码，来看看生成了什么。


#### 汇编


使用你刚刚所生成的汇编代码来创建一个目标文件：



```
$ as -o hellogcc.o hellogcc.s

$ ls
hellogcc.c hellogcc.i hellogcc.o hellogcc.s

```

#### 链接


要生成一个可执行文件，你必须将对象文件链接到它所依赖的库。这并不像前面的步骤那么简单，但它却是有教育意义的：



```
$ ld -o hellogcc hellogcc.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
ld: hellogcc.o: in function `main`:
hellogcc.c:(.text+0xa): undefined reference to `puts'

```

在链接器查找完 `libc.so` 库后，出现一个引用 `undefined puts` 错误。你必须找出适合的链接器选项来链接必要的库以解决这个问题。这不是一个小技巧，它取决于你的系统的布局。


在链接时，你必须链接代码到<ruby> 核心运行时 <rt>  core runtime </rt></ruby>（CRT）目标，这是一组帮助二进制可执行文件启动的子例程。链接器也需要知道在哪里可以找到重要的系统库，包括 `libc` 和 `libgcc`，尤其是其中的特殊的开始和结束指令。这些指令可以通过 `--start-group` 和 `--end-group` 选项来分隔，或者使用指向 `crtbegin.o` 和 `crtend.o` 的路径。


这个示例使用了 RHEL 8 上的路径，因此你可能需要依据你的系统调整路径。



```
$ ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
    -o hello \
    /usr/lib64/crt1.o /usr/lib64/crti.o \
    --start-group \
        -L/usr/lib/gcc/x86_64-redhat-linux/8 \
        -L/usr/lib64 -L/lib64 hello.o \
        -lgcc \
        --as-needed -lgcc_s \
        --no-as-needed -lc -lgcc \
    --end-group \
    /usr/lib64/crtn.o

```

在 Slackware 上，同样的链接过程会使用一组不同的路径，但是，你可以看到这其中的相似之处：



```
$ ld -static -o hello \
    -L/usr/lib64/gcc/x86_64-slackware-linux/11.2.0/ \
    /usr/lib64/crt1.o /usr/lib64/crti.o hello.o /usr/lib64/crtn.o \
    --start-group \
        -lc -lgcc -lgcc_eh \
    --end-group

```

现在，运行由此生成的可执行文件：



```
$ ./hello
Hello, GCC!

```

### 一些有用的实用程序


下面是一些帮助检查文件类型、<ruby> 符号表 <rt>  symbol tables </rt></ruby> 和链接到可执行文件的库的实用程序。


使用 `file` 实用程序可以确定文件的类型：



```
$ file hellogcc.c
hellogcc.c: C source, ASCII text

$ file hellogcc.o
hellogcc.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

$ file hellogcc
hellogcc: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb76b241d7d00871806e9fa5e814fee276d5bd1a, for GNU/Linux 3.2.0, not stripped

```

对目标文件使用 `nm` 实用程序可以列出 <ruby> 符号表 <rt>  symbol tables </rt></ruby> ：



```
$ nm hellogcc.o
0000000000000000 T main
             U puts

```

使用 `ldd` 实用程序来列出动态链接库：



```
$ ldd hellogcc
linux-vdso.so.1 (0x00007ffe3bdd7000)
libc.so.6 => /lib64/libc.so.6 (0x00007f223395e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2233b7e000)

```

### 总结


在这篇文章中，你了解到了 GCC 编译中的各种中间步骤，和检查文件类型、<ruby> 符号表 <rt>  symbol tables </rt></ruby> 和链接到可执行文件的库的实用程序。在你下次使用 GCC 时，你将会明白它为你生成一个二进制文件所要做的步骤，并且当出现一些错误时，你会知道如何逐步处理解决问题。




---


via: <https://opensource.com/article/22/5/gnu-c-compiler>


作者：[Jayashree Huttanagoudar](https://opensource.com/users/jayashree-huttanagoudar) 选题：[lkxed](https://github.com/lkxed) 译者：[robsean](https://github.com/robsean) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
C is a well-known programming language, popular with experienced and new programmers alike. Source code written in C uses standard English terms, so it's considered human-readable. However, computers only understand binary code. To convert code into machine language, you use a tool called a *compiler*.

A very common compiler is GCC (GNU C Compiler). The compilation process involves several intermediate steps and adjacent tools.

## Install GCC

To confirm whether GCC is already installed on your system, use the `gcc`

command:

```
````$ gcc --version`


If necessary, install GCC using your packaging manager. On Fedora-based systems, use `dnf`

:

```
````$ sudo dnf install gcc libgcc`


On Debian-based systems, use `apt`

:

```
````$ sudo apt install build-essential`


After installation, if you want to check where GCC is installed, then use:

```
````$ whereis gcc`


## Simple C program using GCC

Here's a simple C program to demonstrate how to compile code using GCC. Open your favorite text editor and paste in this code:

```
``````
// hellogcc.c
#include <stdio.h>
int main() {
printf("Hello, GCC!\n");
return 0;
}
```


Save the file as `hellogcc.c`

and then compile it:

```
``````
$ ls
hellogcc.c
$ gcc hellogcc.c
$ ls -1
a.out
hellogcc.c
```


As you can see, `a.out`

is the default executable generated as a result of compilation. To see the output of your newly-compiled application, just run it as you would any local binary:

```
``````
$ ./a.out
Hello, GCC!
```


## Name the output file

The filename `a.out`

isn't very descriptive, so if you want to give a specific name to your executable file, you can use the `-o`

option:

```
``````
$ gcc -o hellogcc hellogcc.c
$ ls
a.out hellogcc hellogcc.c
$ ./hellogcc
Hello, GCC!
```


This option is useful when developing a large application that needs to compile multiple C source files.


## Intermediate steps in GCC compilation

There are actually four steps to compiling, even though GCC performs them automatically in simple use-cases.

- Pre-Processing: The GNU C Preprocessor (
`cpp`

) parses the headers (**#include**statements), expands macros (**#define**statements), and generates an intermediate file such as`hellogcc.i`

with expanded source code. - Compilation: During this stage, the compiler converts pre-processed source code into assembly code for a specific CPU architecture. The resulting assembly file is named with a
`.s`

extension, such as`hellogcc.s`

in this example. - Assembly: The assembler (
`as`

) converts the assembly code into machine code in an object file, such as`hellogcc.o`

. - Linking: The linker (
`ld`

) links the object code with the library code to produce an executable file, such as`hellogcc`

.

When running GCC, use the `-v`

option to see each step in detail.

```
````$ gcc -v -o hellogcc hellogcc.c`


![Compiler flowchart](https://opensource.com/sites/default/files/2022-05/compiler-flowchart.png)

(Jayashree Huttanagoudar, CC BY-SA 4.0)

## Manually compile code

It can be useful to experience each step of compilation because, under some circumstances, you don't need GCC to go through all the steps.

First, delete the files generated by GCC in the current folder, except the source file.

```
``````
$ rm a.out hellogcc.o
$ ls
hellogcc.c
```


### Pre-processor

First, start the pre-processor, redirecting its output to `hellogcc.i`

:

```
``````
$ cpp hellogcc.c > hellogcc.i
$ ls
hellogcc.c hellogcc.i
```


Take a look at the output file and notice how the pre-processor has included the headers and expanded the macros.

### Compiler

Now you can compile the code into assembly. Use the `-S`

option to set GCC just to produce assembly code.

```
``````
$ gcc -S hellogcc.i
$ ls
hellogcc.c hellogcc.i hellogcc.s
$ cat hellogcc.s
```


Take a look at the assembly code to see what's been generated.

### Assembly

Use the assembly code you've just generated to create an object file:

```
``````
$ as -o hellogcc.o hellogcc.s
$ ls
hellogcc.c hellogcc.i hellogcc.o hellogcc.s
```


### Linking

To produce an executable file, you must link the object file to the libraries it depends on. This isn't quite as easy as the previous steps, but it's educational:

```
``````
$ ld -o hellogcc hellogcc.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
ld: hellogcc.o: in function `main`:
hellogcc.c:(.text+0xa): undefined reference to `puts'
```


An error referencing an` undefined puts`

occurs after the linker is done looking at the `libc.so`

library. You must find suitable linker options to link the required libraries to resolve this. This is no small feat, and it's dependent on how your system is laid out.

When linking, you must link code to core runtime (CRT) objects, a set of subroutines that help binary executables launch. The linker also needs to know where to find important system libraries, including libc and libgcc, notably within special start and end instructions. These instructions can be delimited by the `--start-group`

and `--end-group`

options or using paths to `crtbegin.o`

and `crtend.o`

.

This example uses paths as they appear on a RHEL 8 install, so you may need to adapt the paths depending on your system.

```
``````
$ ld -dynamic-linker \
/lib64/ld-linux-x86-64.so.2 \
-o hello \
/usr/lib64/crt1.o /usr/lib64/crti.o \
--start-group \
-L/usr/lib/gcc/x86_64-redhat-linux/8 \
-L/usr/lib64 -L/lib64 hello.o \
-lgcc \
--as-needed -lgcc_s \
--no-as-needed -lc -lgcc \
--end-group
/usr/lib64/crtn.o
```


The same linker procedure on Slackware uses a different set of paths, but you can see the similarity in the process:

```
``````
$ ld -static -o hello \
-L/usr/lib64/gcc/x86_64-slackware-linux/11.2.0/ \
/usr/lib64/crt1.o /usr/lib64/crti.o \
hello.o /usr/lib64/crtn.o \
--start-group -lc -lgcc -lgcc_eh \
--end-group
```


Now run the resulting executable:

```
``````
$ ./hello
Hello, GCC!
```


## Some helpful utilities

Below are a few utilities that help examine the file type, symbol table, and the libraries linked with the executable.

Use the `file`

utility to determine the type of file:

```
``````
$ file hellogcc.c
hellogcc.c: C source, ASCII text
$ file hellogcc.o
hellogcc.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
$ file hellogcc
hellogcc: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb76b241d7d00871806e9fa5e814fee276d5bd1a, for GNU/Linux 3.2.0, not stripped
```


The use the `nm`

utility to list symbol tables for object files:

```
``````
$ nm hellogcc.o
0000000000000000 T main
U puts
```


Use the `ldd`

utility to list dynamic link libraries:

```
``````
$ ldd hellogcc
linux-vdso.so.1 (0x00007ffe3bdd7000)
libc.so.6 => /lib64/libc.so.6 (0x00007f223395e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2233b7e000)
```


## Wrap up

In this article, you learned the various intermediate steps in GCC compilation and the utilities to examine the file type, symbol table, and libraries linked with an executable. The next time you use GCC, you'll understand the steps it takes to produce a binary file for you, and when something goes wrong, you know how to step through the process to resolve problems.

## Comments are closed.