GCC 编译器常用命令练习和背后的故事


一、Linux GCC 常用命令

1.简单编译

//test.c

#include <stdio.h> 
int main(void) 
{
    
     printf("Hello World!\n"); 
return 0; }

在这里插入图片描述
编译指令

gcc test.c -o test

上述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译 (Compilation)、汇编 (Assembly)和连接(Linking)

2.预处理

gcc -E test.c -o test.i 或 gcc -E test.c

可以输出 test.i 文件中存放着 test.c 经预处理之后的代码

3.编译为汇编代码(Compilation)

预处理之后,可直接对生成的 test.i 文件编译,生成汇编代码:

gcc -S test.i -o test.s

4.汇编(Assembly)

gas 汇编器负责将其编译为目标文件

gcc -c test.s -o test.o

5.连接(Linking)

gcc 连接器是 gas 提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生 成可执行文件。附加的目标文件包括静态连接库和动态连接库

gcc test.o -o test

6.多个程序文件的编译

通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用 GCC 能够很好地管理 这些编译单元。假设有一个由 test1.c 和 test2.c 两个源文件组成的程序,为了对它们进行编译,并 最终生成可执行程序 test,可以使用下面这条命令:

gcc test1.c test2.c -o test

7.检错

gcc -pedantic illcode.c -o illcode

-pedantic 编译选项并不能保证被编译程序与 ANSI/ISO C 标准的完全兼容,它仅仅只能用来帮助 Linux 程序员离这个目标越来越近。或者换句话说,-pedantic 选项能够帮助程序员发现一些不符合 ANSI/ISO C 标准的代码,但不是全部,事实上只有 ANSI/ISO C 语言标准中要求进行编译器诊断的 那些情况,才有可能被 GCC 发现并提出警告

8.链接

gcc –L /usr/dev/mysql/lib –lmysqlclient test.o –o test

Linux 下的库文件分为两大类分别是动态链接库(通常以.so 结尾)和静态链接库(通常以.a 结尾), 二者的区别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。

二、GCC 背后的故事

1.准备工作

先创建一 个工作目录 test0,然后 用文本编辑器生成一个 C 语言编写的简单 Hello.c

mkdir test0
cd test0

在这里插入图片描述
再输入vim hello.c,输入代码

#include <stdio.h>
int main(void) 
{
    
     
printf("Hello World! \n"); 
return 0;
 }

在这里插入图片描述

2.编译过程-预处理

使用 gcc 进行预处理

gcc -E hello.c -o hello.i

将源文件 hello.c 文件预处理生成 hello.i
GCC 的选项-E 使 GCC 在进行完预处理后即停止
在这里插入图片描述

3.编译

使用 gcc 进行编译的命令如下:

gcc -S hello.i -o hello.s

在这里插入图片描述

将预处理生成的 hello.i 文件编译生成汇编程序 hello.s
GCC 的选项-S 使 GCC 在执行完编译后停止,生成汇编程序
上述命令生成的汇编程序 hello.s 的代码片段如下所示,其全部为汇编代码。
// hello.s 代码片段

main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp 
.cfi_def_cfa_register 6
movl $.LC0, %edi 
call puts
movl $0, %eax 
popq %rbp
.cfi_def_cfa 7, 8 
ret
.cfi_endproc

4.汇编

使用 gcc 进行汇编的命令如下:

 gcc -c hello.s -o hello.o

在这里插入图片描述

将编译生成的 hello.s 文件汇编生成目标文件 hello.o
GCC 的选项-c 使 GCC 在执行完汇编后停止,生成目标文件

5.链接

gcc hello.c -o hello

在这里插入图片描述

使用动态库进行链接,生成的 ELF 可执行文件的大小(使用 Binutils 的 size 命令查看)和链接的动态库 (使用 Binutils 的 ldd 命令查看)

6.分析 ELF 文件

输入代码

readelf -S hello

ELF 文件的段

There are 29 section headers, starting at offset 0x1930:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000000238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000000254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000000274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000000298  00000298
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000000002b8  000002b8
       00000000000000a8  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000000360  00000360
       0000000000000082  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           00000000000003e2  000003e2
       000000000000000e  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          00000000000003f0  000003f0
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000000410  00000410
       00000000000000c0  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             00000000000004d0  000004d0
       0000000000000018  0000000000000018  AI       5    22     8
  [11] .init             PROGBITS         00000000000004e8  000004e8
       0000000000000017  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000000500  00000500
       0000000000000020  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         0000000000000520  00000520
       0000000000000008  0000000000000008  AX       0     0     8
  [14] .text             PROGBITS         0000000000000530  00000530
       00000000000001a2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         00000000000006d4  000006d4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000000006e0  000006e0
       0000000000000012  0000000000000000   A       0     0     4
  [17] .eh_frame_hdr     PROGBITS         00000000000006f4  000006f4
       000000000000003c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000000730  00000730
       0000000000000108  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000200db8  00000db8
       0000000000000008  0000000000000008  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000200dc0  00000dc0
       0000000000000008  0000000000000008  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000200dc8  00000dc8
       00000000000001f0  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000200fb8  00000fb8
       0000000000000048  0000000000000008  WA       0     0     8
  [23] .data             PROGBITS         0000000000201000  00001000
       0000000000000010  0000000000000000  WA       0     0     8
  [24] .bss              NOBITS           0000000000201010  00001010
       0000000000000008  0000000000000000  WA       0     0     1
  [25] .comment          PROGBITS         0000000000000000  00001010
       0000000000000029  0000000000000001  MS       0     0     1
  [26] .symtab           SYMTAB           0000000000000000  00001040
       00000000000005e8  0000000000000018          27    43     8
  [27] .strtab           STRTAB           0000000000000000  00001628
       0000000000000203  0000000000000000           0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  0000182b
       00000000000000fe  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

反汇编 ELF
由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包 含的指令和数据,需要使用反汇编的方法。 使用 objdump -D 对其进行反汇编如下:

 objdump -D hello

使用 objdump -S
将其反汇编并且将其 C 语言源代码混合显示出来:

 gcc -o hello -g hello.c //要加上-g 选项 
 objdump -S hello

下面是部分结果

6a6:	44 89 ef             	mov    %r13d,%edi
 6a9:	41 ff 14 dc          	callq  *(%r12,%rbx,8)
 6ad:	48 83 c3 01          	add    $0x1,%rbx
 6b1:	48 39 dd             	cmp    %rbx,%rbp
 6b4:	75 ea                	jne    6a0 <__libc_csu_init+0x40>
 6b6:	48 83 c4 08          	add    $0x8,%rsp
 6ba:	5b                   	pop    %rbx
 6bb:	5d                   	pop    %rbp
 6bc:	41 5c                	pop    %r12
 6be:	41 5d                	pop    %r13
 6c0:	41 5e                	pop    %r14
 6c2:	41 5f                	pop    %r15
 6c4:	c3                   	retq   
 6c5:	90                   	nop
 6c6:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
 6cd:	00 00 00 

00000000000006d0 <__libc_csu_fini>:
 6d0:	f3 c3                	repz retq 

Disassembly of section .fini:

00000000000006d4 <_fini>:
 6d4:	48 83 ec 08          	sub    $0x8,%rsp
 6d8:	48 83 c4 08          	add    $0x8,%rsp
 6dc:	c3                   	retq   

三、借助第三方库函数完成代码设计

1.光标库(curses)的主要函数功能

1)cbreak():调用cbreak函数后,除了"Del"和"Ctrl"键外,接受其他所有字符输入。
2)nl (/nonl ):输出时,换行是否作为回车字符。nl函数将换行作为回车符,而nonl函数相反。
3)noecho()/echo():关闭/打开输入回显功能。
4)intrflush(WINDOW *win,bool bf) win为标准输出。当bf为true时输入Break,可以加快中断的响应。但是,有可能会造成屏幕输出信息的混乱。
5)keypad (WINDOW *win, bool bf) : win为标准输出。调用keypad函数后,将可以使用键盘上的一些特殊字符,如方向键,转化成curses.h中的特殊键。
6)refresh():重绘屏幕显示内容。在调用initscr函数后,第一次调用refresh函数会清除屏幕显示。

2.体验即将绝迹的远古时代的 BBS

在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统"(后面会使用)。 然后打开一个cmd命令行窗口,命令行输入 telnet bbs.newsmth.net,以游客身份体验即将绝迹的远古时代的 BBS (一个用键盘光标控制的终端程序)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
勾选
在这里插入图片描述
重启电脑
在这里插入图片描述
输入cmd
在这里插入图片描述
在这里插入图片描述

3.安装 curses 库

在 Ubuntu 系统使用命令行安装 curses 库

sudo apt-get install libncurses5-dev 

在这里插入图片描述

安装后,查看安装目录,可以使用命令:

whereis curses.h
whereis libncurses

在这里插入图片描述

4.参考 “Linux 环境下C语言编译实现贪吃蛇游戏”(http://www.linuxidc.com/Linux/2011-08/41375.htm)

参考那个代码
在这里插入图片描述
将 curses 库链接到可执行文件 snake 中

gcc snake.c -lcurses -o snake

输入编译
在这里插入图片描述
输入./snake运行

./snake

在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_46522844/article/details/109106177