使用GCC编译C程序
内容有所搬运,侵权请联系。
1. 检查是否安装GCC
如果已经安装好GCC,并链接到名为cc的默认C编译器,就会看到编译器版本号和版权信息:
$ cc --version
cc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
有可能已经安装好GCC,但是并没有链接到程序名称cc。可以使用gcc的正式名称来调用它:
$ gcc --version
gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
一些免费的系统软件,如针对macOS的MacPorts和Homebrew,针对Windows的Cygwin和MinGw也包括GCC安装包。
2. 使用GCC编译C程序
对于以下源代码:
/* circle.c */
# include <stdio.h>
double circularArea( double r );
int main() {
double radius = 1.0, area = 0.0;
printf("Areas of Circles\n\n");
printf("Radius Area\n"
"--------------------------------\n");
area = circularArea(radius);
printf("%10.1f %10.2f\n", radius, area);
radius = 5.0;
area = circularArea(radius);
printf("%10.1f %10.2f\n", radius, area);
return 0;
}
double circularArea(double r) {
const double pi = 3.1415926536;
return pi * r * r;
}
执行命令
$ gcc -Wall circle.c
-Wall选项会指示编译器输出警告信息。
如果源代码没有错误,GCC执行完后退出,不会在屏幕上输出任何信息。编译器会在当前工作目录下输出一个默认文件名为a.out的文件。可以执行该文件:
$ ./a.out
Areas of Circles
Radius Area
--------------------------------
1.0 3.14
5.0 78.54
该默认名称的历史原因:“assembler output (汇编程序输出)”的缩写形式。
如果不希望可执行程序的文件名为a.out,可以使用-o选项来指定所希望使用的文件名:
$ gcc -Wall -o circle circle.c
2.1 逐步实现
编译分为以下阶段:预处理(preprocessing)、编译(compiling)、汇编(assembling)和链接(linking)。也可以调用独立的工具,如C预处理器cpp,汇编器as和链接器ld。
2.1.1 预处理
将源代码提交给编译器之前,由预处理器执行命令,展开源代码文件中的宏。在正常情况下,GCC不会保留预处理阶段的中间输出文件。-E选项指示GCC在预处理完毕后即可停止。预处理器的输出会被导出到标准输出流,可以利用-o选项把它输出到文件:
$ gcc -E -o circle.i circle.c
如果源代码包含了较多的头文件,则预处理结果可能庞杂难读。
使用-C选项阻止预处理器删除源文件和头文件中的注释:
$ gcc -E -C -o circle.i circle.c
对于include目录而言,通常的搜索顺序为:
- 包含指定源文件的目录(使用“”引号包含)。
- 采用
-iquote选项指定的目录,依照出现在命令行中的顺序进行搜索。只对#include命令中采用引号的头文件名进行搜索。 - 采用
-I选项指定的目录,依旧按照出现在命令行中的顺序进行搜索。 - 环境变量
CPATH指定的目录。 -isystem选项指定的目录,依旧按照出现在命令行中的顺序进行搜索。- 环境变量
C_INCLUDE_PATH指定的目录。 - 系统默认的
include目录。
2.1.2 编译
编译的核心任务是把C程序翻译成机器的汇编语言(assembly language)。每种CPU架构都有不同的汇编语言。
通常情况下,GCC把汇编语言输出存储到临时文件中,并且在汇编执行完后立刻删除。使用-S选项,让编译程序在生成汇编语言输出后立刻停止。如果没有指定文件名,那么输出文件为文件名.s:
$ gcc -S circle.c
编译器预处理circle.c,将其翻译成汇编语言,并将结果存储在circle.s文件中。
如果想把C语言变量作为汇编语言语句中的注释,可以加上-fverbose-asm选项:
$ gcc -S -fverbose-asm circle.c
2.1.3 汇编
每个机器架构都有自己的汇编语言,GCC调用宿主系统的汇编器,把汇编语言翻译成可执行的二进制代码。
汇编的结果是个对象文件(object file),它包含机器码以执行对应源文件中所定义的函数,同时包含一个符号表(symbol table),描述了源文件中具有外部链接的所有对象。
如果调用GCC同时编译和链接一个程序,那么这个对象文件就仅仅是临时文件,在链接器执行完后就会自动删除。-c选项指示GCC不会自动链接文件,但会生成对象文件:
$ gcc -c circle.c
使用GCC的-Wa选项把命令行选项传递给汇编器。例如,我们希望汇编器使用选项
-as=circle.sym:在一个单独列表中输出模块的符号表,并把该列表输出到文件circle.sym中。
-L:在符号表中包含本地符号。本地符号指具有内部链接的C标识符(与GCC的-L选项不同)。
这些选项形成一个逗号分隔的列表:
$ gcc -v -o circle -Wa,-as=circle.sym,-L circle.c
-v选项可以让GCC输出应用在每个编译步骤上的选项,可以看到编译获得的汇编器命令行(伴随大量辅助信息)。
-g选项让编译器输出调试信息。如果在指定汇编器-a选项的同时加上编译器-g选项,那么编译获得的汇编语言列表会与对应的C源代码并列在一起。
$ gcc -g -o circle -Wa,-a=circle.list,-L circle.c
2.1.4 链接
链接器把多个二进制文件链接成一个单独的可执行文件。在链接过程中,它必须把参考符号用对应的对象实际位置代替,以完成程序中多个模块的外部引用。
标准库的大部分函数通常放在文件libc.a中(文件名后缀.a代表"achieve",获取),或者放在用于共享的动态链接文件libc.so中(文件名后缀.so代表"share object",共享对象)。链接库一般位于/lib/或/usr/lib/中,或者位于GCC默认搜索的其他链接库目录。
有时候,希望使用第三方库:
/******** circle.c : 计算圆的面积,以ncurses控制台输出 ********/
# include <curses.h>
double circularArea( double r );
void circle();
int main() {
/* 设定控制台行为 */
(void)initscr(); // 初始光标系统
keypad(stdscr, TRUE); // 启用键盘映射
(void)nonl(); // 禁用尾行翻译
(void)cbreak(); // 获取单个输入字符
/* 运行circle函数 */
circle();
printw("Press any key to exit. ");
refresh(); // 在屏幕上输出
/* 完成 */
getch(); // 等待用户按下任意键
endwin(); // 关闭ncurses控制台
return 0;
}
void circle() {
double radius = 1.0, area = 0.0;
printw("Areas of Circles\n\n");
printw("Radius Area\n"
"--------------------------------\n");
area = circularArea(radius);
printw("%10.1f %10.2f\n", radius, area);
radius = 5.0;
area = circularArea(radius);
printw("%10.1f %10.2f\n", radius, area);
}
double circularArea(double r) {
const double pi = 3.1415926536;
return pi * r * r;
}
ncurses链接库所在文件的文件名是libncurses.a。(在支持动态链接的系统上,GCC自动使用共享链接库libncurses.so或libncurses.dylib)。
前缀lib和后缀.a是标准的,GCC在命令行-l选项后紧跟基本名称后就会自动加上前缀和后缀。在本例中,基本名称是ncurses。
(以下命令未验证,使用yum安装的ncurses并没有静态库,只有动态库)
通常,GCC会自动在标准库目录下搜索文件,例如/usr/lib/。有三种方式可以链接GCC搜索路径以外的链接库:
- 把链接库作为一般对象文件,为GCC指定该链接库的完整路径与文件名:
$ gcc -o circle circle.c /usr/local/lib/libncurses.a
- 链接在GCC搜索路径以外链接库的方法使用
-L选项,为GCC增加另一个搜索链接库的目录, 可以使用多个-L选项:
$ gcc -o circle -L /usr/local/lib -lncurses circle.c
- 把所需的链接库的目录加到环境变量
LIBRARYPATH中。
将选项传递给链接器。通过-Wl选项和逗号分隔符,将选项传递给链接器:
$ gcc -lncurses -Wl,-Map,circle.map circle.c circulararea.c
传递-Map、circle.map到链接器命令行,指示链接器输出一个链接脚本和一个内存映射。
2.1.5 输出所有中间步骤文件
使用-save-temps选项所生成的中间文件,与对应的源文件具有相同的文件名,但文件扩展名为.i、.s和.o,分别对应预处理输出、汇编语言输出和对象文件。
2.1.6 仅检测语法
使用-fsyntax-only选项仅检测语法。
2.2 多个输入文件
将开头circle.c文件分为circle.c和circulararea.c:
/******** circle.c **********/
# include <stdio.h>
double circularArea( double r );
int main() {
double radius = 1.0, area = 0.0;
printf("Areas of Circles\n\n");
printf("Radius Area\n"
"--------------------------------\n");
area = circularArea(radius);
printf("%10.1f %10.2f\n", radius, area);
radius = 5.0;
area = circularArea(radius);
printf("%10.1f %10.2f\n", radius, area);
return 0;
}
/******* circulararea.c **********/
double circularArea(double r) {
const double pi = 3.1415926536;
return pi * r * r;
}
编译多个源代码文件会生成多个对象文件,对每个对象文件都包含一个源文件的机器码对应与对象的符号表。除非使用-c选项指示GCC只编译不链接,否则GCC会使用临时文件作为对象文件输出:
$ gcc -c circle.c
$ gcc -c circulararea.c
这些命令在当前工作目录下生成两个对象文件:circle.o和circulararea.o。将两个文件名放在同一个GCC命令中效果一样。
一旦所有当前源文件都被编译为对象文件,就可以使用GCC来链接它们:
$ gcc -o circle circle.o circulararea.o -lncurses
.c:C程序源代码。.i:C程序预处理输出。.h:C程序头文件。.s:汇编语言。.S:有C命令的汇编语言。在汇编之前必须先进行预处理。
GCC也支持一下扩展名:.ii、.cc、.cp、.cxx、.cpp、.CPP、.c++、.C、.hh、.H、.m、.mi、.f、.FOR、.F、.fpp、.FPP、.r、.ads、.adb。
一条命令中可以混用不同文件类型:
$ gcc -c circle circulararea.s /usr/lib/libm.a
2.3 动态链接和共享对象文件
共享链接库是一种特殊的对象文件,它可以在运行时被链接到一个程序。
共享链接库的优点:程序的可执行文件更小,便于共享模块更新,内存使用效率高。
若想创建一个共享对象文件,可以使用GCC的-share选项。输入文件必须是一个已存在的对象文件。
$ gcc -c circulararea.c
$ gcc -shared -o libcirculararea.so circulararea.o
链接:
$ gcc -c circle.c
$ gcc -o circle circle.o libcirculararea.so -lncurses
上述命令会创建一个可执行文件,在运行时链接到libcirculararea.so。当然,必须确保程序能够在运行时找到这个共享链接库,也可以将链接库安装到标准目录下,例如/usr/lib/,或者设置一个合适的环境变量,例如LIBRARY_PATH。
如果系统支持动态链接,但不想使用动态链接库,GCC中有两种方式建立静态链接执行文件。
- 使用
-static选项:
$ gcc -static -o circle circle.o circulararea.o -lncurses
- 指定外部链接库的静态链接版本,而不是在命令行中使用
-l选项:
$ gcc -o circle circle.o circulararea.o /usr/lib/libncurses.a
2.4 独立程序
除了GCC命令行中所指定的对象文件和链接库文件,链接器还必须链接系统专用的启动码(setup code),程序需要启动码才能被加载,并与操作系统进行顺利交互。GCC会默认链接对象文件crtbegin.o和crtend.o中的初始化和清理子程序。
3 编译器警告
当编译C程序时,可能会获得来自GCC的两类提示:错误(error)和警告(warning)。
GCC可以对其提供的警告信息充分控制。例如,如果不希望区分警告和错误,可以使用-Werror选项,让GCC遇到任何警告时都停止编译。
可以通过-W开头的选项,来逐个启用大部分GCC警告。例如,当使用switch语句的时候,如果没有对应的default标签,则-Wswitch-default选项会让GCC产生一个警告信息。当两个序列点之间的表达式值受到一个区间中被修改的子表达式的影响时,-Wsequence-point选项会产生一个警告信息。
-Wall选项并不能启用所有的-W选项,有些警告选项必须单独启用。
如果使用-Wall,但是希望取消其中部分警告,可以在单独警告选项中-W后面插入no-来指出禁用这部分警告。例如-Wno-switch-default。
-Wextra会对合法但是有疑问的表达式发出警告,例如:
unsigned int u;
if (u < 0)
{/*这部分永远不会被执行到*/}
4 调试
使用-g选项,可以允许GCC在对象文件和执行文件中包含符号表和源代码行号信息。
也可以在-g后面附加后缀,构成新的命令行选项,该选项指示使用与系统原始格式不同的格式来存储符号信息,以满足特定调试器的需求。例如,-ggdb选项指示选择系统可用的最佳格式,以使用GDB来进行调试。
引用文献:
- C语言核心技术。Peter Prinz, Tony Crawford 著
本文档详细介绍了如何使用GCC编译C程序,从检查GCC安装,到编译过程的预处理、编译、汇编和链接,再到处理多输入文件、动态链接和共享对象文件,以及编译器警告和调试选项的使用。通过实例展示了GCC命令行参数的用法,帮助读者深入理解GCC的工作原理。

被折叠的 条评论
为什么被折叠?



