一、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