1.1 简介
对于.c 格式的C文件,可以采用 gcc 或 g++编译。
对于 .cc、.cpp 格式的C++文件,应该采用 g++进行编译。
常用的选项:
选项 | 作用 |
-c <源文件> | 表示编译源文件(.c,.cpp,.cc等) |
-o <输出文件> | 表示输出目标文件(.o) |
-g | 表示在目标文件中产生调试信息,用于 gdb 调试 |
-D <宏定义> | 编译时将宏定义传入进去 |
-Wall | 打开所有类型的警告。 |
注意:这个编译器编译出来的文件是为了在X86的linux系统环境下运行的。
1.2 gcc的编译过程
过程:预编译à编译à汇编à链接
当我们进行编译的时候,要使用一系列的工具,我们称之为工具链。其中包括:预处理器、编译器、汇编器as、连接器。
一个编译过程可以用图2.1 来表示, 包括下面几个阶段:
(1)预处理:预处理器将对源文件中的宏进行展开。
(2)编译:gcc 将 c 文件编译成汇编文件(.s)。
(3)汇编:as 将汇编文件编译成机器码(.o)。
(4)链接:将目标文件和外部符号进行连接,得到一个可执行二进制文件。
1.3 gcc所支持的后缀
后缀名 | 所对应的语言 |
| 后缀名 | 所对应的语言 |
.c | C原始程序 | .s/.S | 汇编语言原始程序 | |
.C/.cc/.cxx | C++原始程序 | .h | 预处理文件(头文件) | |
.m | Objective.c原始程序 | .o | 目标文件 | |
.i | 已经预处理的C原始程序 | .a/.so | 编译后的库文件 | |
.ii | 已经预处理的C++原始程序 |
|
|
1.4 gcc常用选项
选项 | 含义 |
-E | 只预编译,不做任何处理 |
-c | 只编译不链接,生成目标文件”.o” |
-S | 只编译不会汇编,生成汇编代码”.s” |
-g | 在执行过程中包含标准调试信息 |
-o file | 指定将file文件作为输出文件 |
-v | 打印出编译器内部编译各过程的命令行信息和编译器的版本 |
-I dir | 在头文件的搜索路径列表中添加dir目录 |
例子:
下面以一个很简单的test.c 来探讨这个过程。
#include <stdio.h>
#define NUMBER (1 + 2)
int main()
{
int x = NUMBER;
return 0;
}
预处理: gcc -E test.c -o test.i。
我们用cat 查看 test.i 的内容如下:
...... //大多都是stdio.h文件内容
# 943 "/usr/include/stdio.h" 3 4
# 3 "test.c" 2
int main(void)
{
int x = (1+2);
return 0;
}
我们可以看到,文件中宏定义NUMBER 出现的位置被(1+2)替换掉了,还有把stdio.h的内容都添加进来了。
编译: gcc -S test.i -o test.s。
通过cat test.s 查看test.s 的内容为汇编代码如下:
junjia@junjia:~/works/3rd_day/1st_test$ cat test.s
.file "test.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $16, %esp
movl $3, -4(%ebp)
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4"
.section .note.GNU-stack,"",@progbits
junjia@junjia:~/works/3rd_day/1st_test$
汇编: as test.s -o test.o 。
利用as 将汇编文件编译成机器码。得到输出文件为 test.o,test.o 中为目标机器上
的二进制文件。
用 nm 查看文件中的符号: nm test.o ,输出如下:
00000000 T main
有的编译器上会显示:
00000000 b .bss 00000000 d .data 00000000 t .text U ___main U __alloca 00000000 T _main
既然已经是二进制目标文件了,能不能执行呢?
试一下./test.o,提示 cannot execute binary file。
因为是有U这样的符号的地址没有定下来。
链接:gcc -o test test.o。
将所有的.o 文件链接起来生成可执行程序,这个时候的test就可以执行了。
一般我们编译文件没有必要那么多步骤,只要如下就够了:
gcc -o teat test.c
步骤总结:
预处理阶段:对包含的头文件(#include)和宏定义( #define、 #ifdef 等)进行处理。
gcc –E hello.c –o hello.i
//-o 表示输出为指定文件类型 -E 将源文件( *.c) 转换为( *.i)
编译阶段:检查代码规范性、语法错误等,在检查无误后把代码翻译成汇编语言。
gcc –S hello.i –o hello.s
//-S 将已预处理的 C 原始程序( *.i)转换为( *.s)
链接阶段:将.s 的文件以及库文件整合起来链接为可执行程序。
gcc –o hello.exe hello.s
//最后将汇编语言原始程序(*.s)和一些库函数整合成( *.exe)
示例1:几种编译过程。
#include <stdio.h>
#define MAX 100
#define max(a, b) ((a) > (b) ? (a) : (b)) //宏定义,执行-E 之后被替换
main()
{
printf("MAX=%d\n", MAX);
printf("max(3,4)=%d\n", max(3, 4));
}
//法一:
gcc –E project1.c –o project1.i //预编译,生成已预编译过的 C 原始程序*.i
gcc –S project1.i –o project1.s //编译,生成汇编语言原始程序*.s
gcc –o project1.exe project1.s //链接,生成可执行程序
//法二:
gcc –c project1.c –o project1.o //编译
gcc –o project1.exe project1.o //链接
//法三:
gcc –o project1.exe project1.c //编译并链接
示例2:-D选项的使用。
#include <stdio.h>
main()
{
#ifdef lry //表示如果定义了 lry,即命令行参数传了 lry,就执行下面的输出
printf("lry is defined!\n");
#else
printf("lry is not defined!\n");
#endif
printf("main exit\n");
}
编译过程:
gcc –E project2.c –o project2.i –D lry
//条件编译,用-D 传递,如果没有传 lry 则执行#else
gcc –S project2.i –o project2.s
gcc –o project2.exe project2.s
或:gcc –o project2 project2.c –D lry
1.5 gdb调试器
作用:对c/c++程序进行调试的。
1.让程序停止在指定的某处
2.当程序停住时,可以查看程序的运行状态(比如:变量值)
gdb命令&调试步骤
第1步:编译程序
gcc -g a.c -o a.out
第2步:启动GDB
gdb a.out //运行调试器并打开调试文件
或者:
gdb //运行gdb
file a.out //打开调试文件
第3步:打断点
break 函数名 //在函数入口处打断点
或者
break 行号 //在某行打断点
第4步:运行程序
run
其他命令:括号内是命令的简写。
序号 | 命令 | 作用 |
1 | next,continue |
|
2 | list(l) |
|
3 | break(b) | 函数名/行号/文件名:行号 |
4 | break行号 if 条件 | break 5 if k=8 |
5 | info break | 查看断点信息 |
6 | delete <断点编号> | 删除断点 |
7 | run(r) | 全速运行到断点处 |
8 | next(n) | 单独运行,但不进入子函数 |
9 | step(s) | 单独运行,但进入子函数 |
10 | continue(c) |
|
11 | print(p) |
|
12 | finish |
|
13 | watch 变量名 | 查看变量的值 |
下一小节:Makefile的使用