无论是 C 还是 C++ 程序,将其从源代码转变为可执行代码的过程,具体可分为预处理 Preprocessing,编译 Compilation,汇编 Assembly,链接 Linking 这四个阶段。
默认情况下 GCC 指令会直接将源代码历经的四个阶段一气呵成地转变为可执行二进制代码,并且不会保留每个阶段产生的中间文件。
(1) 预处理生成 .i
文件,对这类文件执行编译就能生成 .s
文件。
(2) 编译生成 .s
文件,对这类文件执行汇编就能生成 .o
文件。
(3) 汇编生成 .o
文件,对这类文件执行连接就能生成可执行文件。
(4) 连接生成可执行二进制程序(例如 .exe
文件)。
我们使用 GCC 编译一个源代码文件的时候,可以不用逐阶段的去编译,而是可以让 GCC 将源代码文件一步到位编译到指定阶段,例如使用 -c
选项可以将源代码直接编译到 .o
文件,被跳过的预处理,编译阶段并不是不用执行,而是 GCC 会自动帮你执行跳过的阶段。
GCC 的命令编写快捷灵活,支持多个命令选项同时组合使用时可以无序或任意顺序排列需要用到的选项,即 GCC 没有要求用户在使用命令时需要按某个固定的顺序组合排列指定的命令选项。这样不需要用户在记住大量命令和选项的同时还需要记住多个选项同时使用时的排放顺序,所以大大提高了用户使用的便捷性命令的灵活性。
例如使用 GCC 编译 HelloWorld.c 文件,仅预处理成 .o
文件,并使用 -o
选项指定编译得到的目标文件名为 hello.o,那么我们编写该编译命令时命令可以这样 gcc HelloWorld.c -c -o hello.o
或这样 gcc -c -o hello.o HelloWorld.c
还可以这样 gcc -c HelloWorld.c -o hello.o
。
所以我们可以从中看出 GCC 的命令在指定一些选项时不需要按照规定某一个固定的顺序放置,可以由用户自由随机指定顺序放置。
但是有一点需要注意的是,如果需要指定含参数的选项,那么该选项的参数必须紧紧跟随在该选项的后面,比如指定目标文件名 -o hello
所以 hello
是紧紧跟随在 -o
后方的,参数在后方的同时不能被其他选项分隔的,例如这指定目标文件名 -o -c hello
选项 -o
的参数 hello
被其他的选项 -c
分隔了,所以这样命令格式就是错误的。
文章开头提到过默认情况下 GCC 会直接将源代码转变为可执行文件需要的四个阶段一气呵成地执行完成,换句话说就是源码编译后直接就得到了可执行文件,虽然一气呵成但实际上还是逐步地执行了四个编译阶段的,只是在每个阶段不会输出中间临时文件让我们看见,非常的干净利落。
如果源程序仅有一个文件这完全没有任何问题,然而实际情况是源程序为了实现模块化一般都被拆分为多个文件组成,所以实际上每个文件都不是一个完整的程序,所以每个文件无法被单独编译成可执行文件。
所以通常做法是将每个源代码文件汇编成 .o
文件(即仅执行三个编译阶段到汇编阶段),最后将这些 .o
文件链接成一个完整的可执行文件(例如 .exe
文件)。一般情况下仅输出每个源代码的 .o
文件,不输出 .i
和 .s
文件,当然也可以输出,只是输出了也用不着,连接时不需要它们。所以你会发现一般 IDE 编译输出目录下包含每个源代码的 .o
文件加一个最终的可执行文件。
所以要求 GCC 的编译过程可定制,例如让 GCC 将源代码编译到哪个阶段就编译到哪个阶段,由我们决定是否输出中间文件。而非默认将源代码一气呵成的编译成可执行文件。所以为了灵活应用 GCC 为我们提供了丰富的命令以及选项,供我们定制化编译流程,下面我们来详细看看 GCC 的命令以及命令参数。
GCC 的命令和选项多而复杂,所以不建议去逐个的记忆这些命令,实际上也无法记住。所以只要求记忆常用命令和选项,其余命令有大致了解即可,实际使用到复杂命令时(例如编写 Makefile 时)再查阅这些命令使用方法即可。
这里需要注意:命令选项分为带参数和不带参数两种,带参数选项需要跟随参数,例如 -o
选项,命令选项区分大小写,还要注意有些选项的参数还需要与选项贴合,例如 -O3
,-lm
。
命令和选项虽多但是大致分为:高频使用选项,警告信息控制选项,优化,输出文件,预处理,其他编译选项这六大类。
(1) -c
,执行汇编阶段,生成 object 目标文件。
(2) -E
,执行预处理阶段,生成预处理文件。
(3) -S
,执行编译阶段,生成汇编代码文件。
(4) -o [FILE]
,生成指定的输出文件,用在生成可执行文件时。
(5) -O0
,不进行优化处理。
(6) -O
,等同于 -O1
优化生成代码。
(7) -O2
,比 -O1 进一步优化。
(8) -O3
,比 -O2 更进一步优化,包括 inline 函数。
(9) -w
,不生成任何警告信息。
(10) -W
,只生成编译器认为会出现错误的警告。
(11) -Wall
,生成所有警告信息。
(12) -g
,生成调试信息,GNU 调试器调试时可利用该信息。
(13) -ansi
,只支持 ANSI 标准的 C 语法,这一选项将禁止 GNU C 的一些些特色,例如 asm 或 typeof 关键词。
(14) -DMACRO
,以字符串 “1” 定义 MACRO 宏。
(15) -DMACRO=DEFN
,以字符串 “DEFN” 定义 MACRO 宏。
(16) -IDIRECTORY
,指定额外的头文件搜索路径 DIRECTORY。
(17) -LDIRECTORY
,指定额外的函数库搜索路径 DIRECTORY。
(18) -shared
,生成共享目标文件。通常用在建立共享库时。
(19) -static
,静态连接,即禁止使用共享连接。
(20) -UMACRO
,取消对 MACRO 宏的定义。
高频使用选项由下面各类选项中提取而来,所以你会发现这里列出的一些选项与后面小节列出的选项存在重复。
(1) -w
,不生成任何警告信息。
(2) -W
,只生成编译器认为会出现错误的警告。
(3) -Wall
,生成所有警告信息。
(4) -Waddress
,使用可疑的内存地址时给出警告。
(5) -Waggregate-return
,当返回结构,联合或数组时给出警告。
(6) -Waliasing
,为可能的虚参重叠给出警告。
(7) -Walign-commons
,对 COMMON 块对齐的警告。
(8) -Wampersand
,若延续字符常量中缺少 &
则给出警告,不支持 C 语言。
(9) -Warray-bounds
,当数组访问越界时给出警告。
(10) -Warray-temporaries
,创建临时数组时给出警告,不支持 C 语言。
(11) -Wattributes
,当对属性的使用不合适时给出警告。
(12) -Wbad-function-cast
,当把函数转换为不兼容类型时给出警告。
(13) -Wbuiltin-macro-redefined
,当内建预处理宏未定义或重定义时给出警告。
(14) -Wc++-compat
,当在 C 语言中使用了 C 与 C++ 交集以外的构造时给出警告。
(15) -Wcast-align
,当转换指针类型导致对齐边界增长时给出警告。
(16) -Wcast-qual
,当类型转换丢失限定信息时给出警告。
(17) -Wchar-subscripts
,当下标类型为 char 时给出警告。
(18) -Wenum-compare
,对不同枚举类型之间的比较给出警告。
(19) -Wformat
,对 printf/scanf/strftime/strfmon 中的格式字符串异常给出警告
(20) -Wfloat-equal
,当比较浮点数是否相等时给出警告。
(21) -Wignored-qualifiers
,当类型限定符被忽略时给出警告。
(22) -Wimplicit
,对隐式函数声明给出警告。
(23) -Wimplicit-function-declaration
,对隐式函数声明给出警告。
(24) -Wimplicit-int
,当声明未指定类型时给出警告。
(25) -Winline
,当内联函数无法被内联时给出警告。
(26) -Wmain
,对可疑的 main 声明给出警告。
(27) -Wswitch-enum
,当使用枚举类型作为 switch 变量但又缺少某个 case 时给出警告。
(28) -Wuninitialized
,自动变量未初始化时警告。
(29) -Wunused-value
,当一个表达式的值未被使用时给出警告。
(30) -Wunused-variable
,有未使用的变量时警告。
(31) -Wvla
,使用变长数组时警告。
(32) -Wreturn-type
,当 C 函数的返回值默认为 int,或者 C++ 函数的返回类型不一致时给出警告。
(33) -Wredundant-decls
,对同一个对象多次声明时给出警告。
(34) -Wold-style-cast
,程序使用 C 风格的类型转换时给出警告,不支持 C 语言。
(35) -Wempty-body
,当 if 或 else 语句体为空时给出警告。
(36) -Winit-self
,对初始化为自身的变量给出警告。
(37) -Wlong-long
,当使用 -pedantic 时不对 long long 给出警告。
(38) -Wmissing-prototypes
,全局函数没有原型时给出警告。
(39) -Wnonnull
,当将 NULL 传递给需要非 NULL 类型的参数的函数时给出警告。
(40) -Wparentheses
,可能缺少括号的情况下给出警告。
(41) -Wunused
,启用所有关于 xx未使用 的警告。
(42) -Wvolatile-register-var
,当一个寄存器变量被声明为 volatile 时给出警告。
(43) -Wundef
,当 #if 指令中用到未定义的宏时给出警告。
(44) -Wenum-compare
,对不同枚举类型之间的比较给出警告。
(45) -Wdouble-promotion
,对从 float 到 double 的隐式转换给出警告。
(46) -Wnested-externs
,当 extern 声明不在文件作用域时给出警告。
(47) -Wformat-zero-length
,对长度为 0 的格式字符串给出警告。
(48) -Wendif-labels
,当 #elif 和 #endif 后面跟有其他标识符时给出警告。
(49) -Wdiv-by-zero
,对编译时发现的零除给出警告。
(51) -Wunused-macros
,当定义在主文件中的宏未被使用时给出警告。
(52) -Wunused-function
,有未使用的函数时警告。
(53) -Wswitch-default
,当使用枚举类型作为开关变量,但没有提供 default 语句时给出警告。
(54) -Wunused-label
,有未使用的标号时警告。
(55) -Wprotocol
,当继承来的方法未被实现时给出警告,不支持 C 语言。
(1) -O0
,不进行优化处理。
(2) -O
,即等同于 -O1,在不影响编译速度的前提下,尽量采用一些优化算法降低代码大小,和提高目标代码运行速度。
(3) -O2
,该优化选项会牺牲部分编译速度,采用几乎所有的目标配置支持的优化算法以提高目标代码运行速度。
(4) -O3
,最大化速度(maximize speed),会增加代码大小,即提高目标代码的并行执行能力。
(5) -Os
,优选代码空间(favor code space)即优化生成目标文件的大小。
(6) -Og
,启用全局优化(enable global optimization),为了能够生成更好的调试信息,精心挑选部分与调试不冲突的优化选项。
(7) -Ofast
,使用最高级别的优化选项来编译代码,可以无视严格的语言标准以提高程序的执行速度。
般来说,如果不指定优化标识的话,GCC 就会产生可调试代码,每条指令之间将是独立的,可以在指令之间设置断点,使用 GDB 中的 p 命令查看变量的值,改变变量的值等,并且把获取最快的编译速度作为它的目标。
(1) -Fa [file]
,命名程序集列表文件(name assembly listing file。
(2) -Fo
,命名对象文件(name object file。
(3) -FA [sc]
,配置程序集列表(configure assembly listing。
(4) -Fp
,命名预编译头文件(name precompiled header file。
(5) -Fd [file]
,命名 .PDB 文件(name .PDB file)。
(6) -Fr [file]
,命名源浏览器文件(name source browser file。
(7) -Fe
,命名可执行文件(name executable file)。
(8) -FR [file]
,命名扩展 .SBR 文件(name extended .SBR file)。
(9) -Fm [file]
,命名映射文件(name map file)。
(1) -FI
,命名强制包含文件(name forced include file)。
(2) -C
,不吸取注释(don’t strip comments)。
(3) -U
,移除预定义宏(remove predefined macro)。
(4) -D {=|#}
,定义宏(define macro)。
(5) -u
,移除所有预定义宏(remove all predefined macros)。
(6) -E
,将预处理定向到标准输出(preprocess to stdout)。
(7) -I
,添加到包含文件的搜索路径(add to include search path)。
(8) -EP
,将预处理定向到标准输出,不要带行号(preprocess to stdout, no #line)。
(9) -X
,忽略 “标准位置”(ignore “standard places”)。
(10) -P
,预处理到文件(preprocess to file)。
(1) --version
,显示 GCC 详细版本信息。
(2) -o [FILE]
,生成指定的输出文件,用在生成可执行文件时。
(3) -c
,执行汇编阶段,生成 object 目标文件。
(4) -E
,执行预处理阶段,生成预处理文件。
(5) -S
,执行编译阶段,生成汇编代码文件。
(6) -x [language] [file name]
,根据约定 C 语言的后 缀名称是 .c ,如果不是 .c,则需要使用该选项指定文件类型,例如 -x c++ hello.a。
(7) -w
,不生成任何警告信息。
(8) -W
,只生成编译器认为会出现错误的警告。
(9) -Wall
,生成所有警告信息。
(10) -g
,生成调试信息到目标文件中,将编译时的调试信息保存到本地文件中(stabs,COFF,XCOFF,DWARF)。
(11) -ggdb
,为 GDB 产生调试信息,包含 GDB 的扩展。
(12) -ggdb[level]
,设定产生何种等级的调试信息, level 为 1-3,1 最少,3 最多,例如 -ggdb3。
(13) -pipe
,在多个编译过程之间使用管道。
(14) -static
,静态连接,即禁止使用共享连接。
(15) -ansi
,C 模式下支持所有 ISO C90 标准的 C 程序, C++ 模式下去除对 GNU C++ 扩展的支持(GNU 扩展会与 ISO C++ 冲突)。
(16) -std=
,确定编译语言的标准,例如 -std=c11,目前只在编译 C 和 C++ 时有效 -fno-asm 不将 asm,inline,typeof 作为关键字,可以用他们做变量名等。
(17) -funsigned-char
,将 char 的数据类型设为 unsigned,即无符号。
(18) -fsigned-char
,正好相反,将 char 设为 signed,即有符号。
(19) -fsyntax-only
,只检查语法错误,不做其他任何事。
(20) -pedantic
,显示所有的 ISO C 和 ISO C++ 的警告,并且拒绝所有使用禁止扩展的程序。
(21) -ftime-report
,统计编译消耗的时间并显示报告。
(22) -fmem-report
,显示所有的静态内存分配。
(23) -ftest-coverage
,为 gcov 工具产生数据文件。
(24) -DMACRO
,以字符串 “1” 定义 MACRO 宏。
(25) -DMACRO=DEFN
,以字符串 “DEFN” 定义 MACRO 宏。
(26) -IDIRECTORY
,指定额外的头文件搜索路径 DIRECTORY。
(27) -LDIRECTORY
,指定额外的函数库搜索路径 DIRECTORY。
(28) -shared
,生成共享目标文件。通常用在建立共享库时。
(29) -UMACRO
,取消对 MACRO 宏的定义。
(30) -M
,生成文件关联的信息。包含目标文件所依赖的所有源代码
(31) -MM
,和 -M 一样,但是它将忽略由 #include 造成的依赖关系。
(32) -MD
,和 -M 相同,但是输出将导入到 .d 文件。
(33) -MMD
,和 -MM 相同,但是输出将导入到 .d 文件。
(34) -I [dir]
,表示将 dir 目录添加到头文件搜索路径中,这样就可以直接使用 #include
(35) -I-[dir]
,表示将指定的目录从头文件搜索路径中移除,移除后将不再该路径搜索头文件。
(36) -idirafter [dir]
,在指定的头文件搜索路径里面查找失败,则到这里指定的目录中查找。
(37) -L [dir]
,制定编译的时候,指定库的搜索路径,告诉编译器在 dir 目录中查找库文件,例如指定搜索我们自定义库。
(38) -l[library name]
,指定编译时候使用的库,例如指定数学库 gcc hello.c -lm
。
命令:gcc [OPTIONS] [FILE]
,其中 OPTIONS 表示选项/参数,FILE 表示相关文件的名称,文件或选项可以多个同时使用,多个文件或选项顺序随意,依次列出即可,例如:gcc hello.c happly.c -o hello.exe -Ofast -g -Wall。
命令实例:
(1) 使用 GCC 编译程序很简单,直接将源代码文件名提供给 GCC,即可编译出可目标可执行文件,在不使用任何选项的情况下默认将编译到可执行文件阶段,并且输出的目标可执行文件名默认为 a.exe
或 a.out
。
gcc ./hello.c
(2) 使用 -o
选项来定义直观的目标文件名,这里定义目标文件名为 hello.exe
这时输出目标可执行文件名就不再是 a.exe
或 a.out
了。
gcc ./hello.c -o hello.exe
(3) 使用 -E
选项定制编译阶段,让 GCC 编译源代码文件时只要执行到预处理阶段即可,即执行预处理阶段后就停止并输出预处理阶段文件。
gcc -E ./hello.c -o hello.i
(4) 让 GCC 将预处理文件编译成汇编文件,即执行编译阶段后就停止并输出编译阶段得到的汇编文件。
gcc -S ./hello.i -o hello.s
(4) 让 GCC 将汇编文件汇编成 object 文件,即执行汇编阶段后就停止并输出汇编阶段得到的 object 文件。
gcc -c ./hello.s -o hello.o
(5) 让 GCC 将 object 文件连接成目标可执行文件,GCC 连接 object 文件没有相应的控制选项。
gcc ./hello.o -o hello.exe
(6) 也可以一步到位(使用一条命令)将源代码文件编译到汇编阶段,被跳过的预处理和汇编阶段由 GCC 帮我们执行。
gcc -c ./hello.c -o hello.o
(7) 开启编译时语法问题警告,例如这里开启了变量或函数处于隐式声明时输出相应警告,以及 #if 语句使用了未定义的宏定义时输出相应警告,开启后如果源代码存在语法问题就会在命令终端输出相应警告了。
gcc hello.c -o hello -Wimplicit -Wundef
(8) 如果需要启动优化,优化等级为 3,以及还需要生成调试信息到目标文件,方便使用 GDB 调试。开启优化后编译器会按照提供的选项执行相应等级的优化,启动生成调试信息后目标文件体积会明显增加。
gcc hello.c -o hello -g -O3
使用 GCC 需要对编译的四个阶段(编译过程),以及编译的规则有一定的了解,熟悉使用 GCC 对于编写 Makefile 具有决定性的作用。
GCC 的命令使用非常灵活,命令都以 gcc 开头然后配合待编译文件名,编译选项即可(可以说 GCC 就只有一条命令,只是编译选项较多而已)。命令选项可以随意组合,只需要注意选项是否包含参数即可,如果选项包含参数则参数需要紧紧跟随在选项后方。
以上就是 GCC 的一些参数及命令使用,熟悉 GCC 命令和参数有助于编写 Makefile,编写 Makefile 的前提就是就要熟悉 GCC 的命令,下一篇将会讲解 Makefile 相关的话题,深入的去理解 Makefile 的语法,敬请期待吧,如果以上内容对你有帮助就帮忙点点赞呗。