内容简介:GNU 编译器套件(Ubuntu、Mint 等使用 deb 格式软件包的 Linux 发行版通常会默认安装 GCC 编译器,但是由于相关的软件包可能并不完整,因此可以通过如下命令安装完整的 GCC 编译环境。由于当前需要编译的是 C 语言程序,因此需要使用到
GNU 编译套件
GNU 编译器套件( GCC , GNU Compiler Collection )最初的目标是作为一款 GNU 操作系统的通用编译器,包含有 C、C++、Objective-C、Objective-C++、Fortran、Ada、 Go 、BRIG(HSAIL)等语言的前端及其相关的 libstdc++
、 libgcj
等库,目前已经移植到 Windows、Mac OS X 等商业化操作系统。GCC 编译器套件当中包含了诸多的软件包,主要的软件包如下面表格所示:
名称 | 描述 |
---|---|
cpp | C 预处理器。 |
gcc | C 编译器。 |
g++ | C++ 编译器。 |
gccbug | 用于创建 BUG 报告的 Shell 脚本。 |
gcov | 覆盖测试工具,用于分析程序需要优化的位置。 |
libgcc | GCC 运行库。 |
libstdc++ | 标准 C++库。 |
libsupc++ | C++语言支持函数库。 |
Ubuntu、Mint 等使用 deb 格式软件包的 Linux 发行版通常会默认安装 GCC 编译器,但是由于相关的软件包可能并不完整,因此可以通过如下命令安装完整的 GCC 编译环境。
sudo apt-get install build-essential
基本使用
由于当前需要编译的是 C 语言程序,因此需要使用到 gcc
软件包提供的命令,这些命令的基本使用格式如下:
gcc [-options] [filename]
将上一节编写的 Hello World 程序保存至一个 main.c
源代码文件当中,然后执行 gcc
编译命令得到可执行的 a.out
文件:
➜ gcc main.c ➜ ls a.out main.c ➜ ./a.out hello world!
如果需要指定输出的可执行文件名称,那么可以添加 -o
选项:
➜ gcc main.c -o main ➜ ls main main.c ➜ ./main hello world!
编译信息
如果需要查看编译的过程,那么可以使用 -v
命令选项:
➜ gcc -v main.c Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.3.0-27ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04) COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/7/cc1 -quiet -v -imultiarch x86_64-linux-gnu main.c -quiet -dumpbase main.c -mtune=generic -march=x86-64 -auxbase main -version -fstack-protector-strong -Wformat -Wformat-security -o /tmp/cc9yluga.s GNU C11 (Ubuntu 7.3.0-27ubuntu1~18.04) version 7.3.0 (x86_64-linux-gnu) compiled by GNU C version 7.3.0, GMP version 6.1.2, MPFR version 4.0.1, MPC version 1.1.0, isl version isl-0.19-GMP ... ...
优化选项
GCC 编译优化等级由低到高分为 -O0
、 -O1
、 -O2
、 -O3
, o
即单词 Optimization 的首字母,不同优化等级下得到的文件体积与执行效率各不相同。此外,嵌入式开发当中还经常使用到一个 -Os
,其优化等级介于 -O2
和 -O3
之间。
➜ gcc -os main.c -o main ➜ ll 总用量 16K -rwxrwxr-x 1 hank hank 8.2K 2月 24 17:51 main -rw-rw-r-- 1 hank hank 136 2月 21 18:12 main.c
调试信息
开启优化选项后编译的代码,并不会保留任何关于调试与 debug 的信息,如果需要保留这些信息,可以开启 -g
选项( gdb ),此时得到的文件体积会增大。
➜ gcc main.c ➜ ll -rwxrwxr-x 1 hank hank 8.2K 2月 24 17:58 a.out ➜ gcc -g main.c ➜ ll -rwxrwxr-x 1 hank hank 11K 2月 24 17:58 a.out
包含头文件
Linux C 语言程序当中存在如下两种头文件的包含情况:
-
#include <head.h>
:预处理程序会在 编译系统指定的目录 当中去搜索头文件。 -
#include "head.h"
:预处理器会在当前 目标文件所在的文件夹内 搜索头文件,如果未找到则进入编译系统指定目录搜索。
GCC 当中可以通过 -I
参数( include )将指定目录添加到头文件的搜索列表当中:
➜ gcc -v main.c -I /workspace #include "..." search starts here: #include <...> search starts here: /workspace /usr/lib/gcc/x86_64-linux-gnu/7/include /usr/local/include /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed /usr/include/x86_64-linux-gnu /usr/include
编译步骤
实质上从 hello.c
源代码到 hello
或 a.out
可执行文件,GCC 的编译过程大致经历了下面 4 个步骤:
- 预处理 :C 编译器对各种预处理命令进行处理,包括头文件包含、宏定义的扩展、条件编译的选择等( 使用
gcc -E
);
➜ gcc -E main.c -o main.i ➜ ls main.c main.i
- 编译 :对预处理得到的源代码文件进行翻译转换,产生由机器语言描述的 汇编文件 ( 使用
gcc -S
);
➜ gcc -S main.i ➜ ls main.c main.i main.s
- 汇编 :将汇编代码转译成为 机器码 ( 使用
gcc -c
);
➜ gcc -c main.s ➜ ls main.c main.i main.s main.o
- 链接 :将机器码中的各种符号引用与定义转换为 可执行文件 中的相应信息(如虚拟地址)。
➜ gcc main.o -o main ➜ ls main.c main.i main.o main.s main
为了便于查找,下表列出了编译和链接 C/C++ 程序时各类文件扩展名的释义:
后缀名称 | 描述内容 |
---|---|
.c |
C 语言源码,必须经过预处理。 |
.C 、 .cc 、 .cxx |
C++源代码,必须经过预处理。 |
.h |
C/C++语言源代码的头文件。 |
.i |
由 .c 文件预处理后生成。 |
.ii |
由 .C 、 .cc 、 .cxx 源码预处理后生成。 |
.s |
汇编语言文件,是 .i 文件编译后得到的中间文件。 |
.o |
目标文件,是编译过程得到的中间文件。 |
.a |
由目标文件构成的文件库,也称为 静态库 。 |
.so |
共享对象库,也称为 动态库 。 |
链接库文件
GCC 对于库文件的链接存在 动态 和 静态 两种方式:
- 静态链接方式 :使用 静态链接库 进行链接,由于包含了程序运行所需的所有库,因此生成的文件体积较大但是能够直接运行。
- 动态链接方式 :使用 动态链接库 进行链接,类似于 Windows 系统下的
.dll
文件,执行时需要依赖相应的动态链接库。
GCC 默认使用动态链接 -shared
方式,可以通过加入 -static
参数来指定使用静态链接方式。注意观察下面以不同方式编译代码后,所得到的可执行文件的体积:
➜ gcc main.c ➜ ll 总用量 16K -rwxrwxr-x 1 hank hank 8.2K 2月 22 18:25 a.out -rw-rw-r-- 1 hank hank 136 2月 21 18:12 main.c ➜ gcc main.c -static ➜ ll 总用量 832K -rwxrwxr-x 1 hank hank 825K 2月 22 18:25 a.out -rw-rw-r-- 1 hank hank 136 2月 21 18:12 main.c
创建静态链接库
静态链接库是由 GCC 在汇编阶段产生的 .o
文件构成的集合,以 .a
作为文档后缀名称,Linux 下也称存档( archive ),通常使用 ar
工具命令来进行打包管理。
➜ ls func1.c func2.c main.c ➜ gcc -c func1.c func2.c ➜ ls func1.c func1.o func2.c func2.o main.c ➜ ar -r libmain.a func1.o func2.o ar: 正在创建 libmain.a ➜ ll 总用量 36K -rw-rw-r-- 1 hank hank 84 2月 24 20:06 func1.c -rw-rw-r-- 1 hank hank 1.6K 2月 24 20:16 func1.o -rw-rw-r-- 1 hank hank 84 2月 24 20:16 func2.c -rw-rw-r-- 1 hank hank 1.6K 2月 24 20:16 func2.o -rw-rw-r-- 1 hank hank 3.3K 2月 24 20:16 libmain.a -rw-rw-r-- 1 hank hank 136 2月 21 18:12 main.c
创建动态链接库
动态链接库也称为共享对象( shared object ),通常以 .so
作为文件后缀名,由 GCC 编译器通过添加 -fpic
参数( pic 指位置独立代码,即 Position Independent Code 缩写 )方式生成,共享对象模块的每个地址( 函数调用和变量引用 )都是相对地址,允许程序在执行时动态的加载与运行。
➜ ls func1.c func2.c main.c ➜ gcc -c -fpic func1.c func2.c ➜ ls func1.c func1.o func2.c func2.o main.c ➜ gcc -shared func1.o func2.o -o libmain.so ➜ ll 总用量 28K -rw-rw-r-- 1 hank hank 84 2月 24 20:06 func1.c -rw-rw-r-- 1 hank hank 1.6K 2月 24 21:31 func1.o -rw-rw-r-- 1 hank hank 84 2月 24 20:16 func2.c -rw-rw-r-- 1 hank hank 1.6K 2月 24 21:31 func2.o -rwxrwxr-x 1 hank hank 7.8K 2月 24 21:32 libmain.so -rw-rw-r-- 1 hank hank 136 2月 21 18:12 main.c
上面的步骤比较繁琐,可以将 汇编 和 链接 两条命令合并为一条命令,编译 C 语言代码的同时得到 .so
动态链接库文件。
➜ ls func1.c func2.c main.c ➜ gcc -fpic -shared func1.c func2.c -o libmain.so ➜ ls func1.c func2.c main.c libmain.so
指定编译规范
由于 GCC 同时支持多套 C 程序语言规范,因而编译时可以通过选项指定当前需要遵循的语言规范,具体请参考下表:
规范 | 规范 | 选项 | 补充 |
---|---|---|---|
C89 / C90 | ANSI C (X3.159-1989) 或 ISO/IEC 9899:1990 | -std=c90 |
-std=iso9899:1990 、 -ansi |
C94 / C95 | 95 年发布的 C89/C90 修正版,此次修正通常称作 AMD1 | - | -std=iso9899:199409 |
C99 | ISO/IEC 9899:1999 | -std=c99 |
-std=iso9899:1999 |
C11 | ISO/IEC 9899:2011 | -std=c11 |
-std=iso9899:2011 |
GNU C89 / C90 | 带 GNU 扩展的 C89/C90 | -std=gnu90 |
- |
GNU C99 | 带 GNU 扩展的 C99 | -std=gnu99 |
- |
GNU C11 | 带 GNU 扩展的 C11 | -std=gnu11 |
- |
例如下面代码当中,指定了 GCC 的编译过程遵循 C89/C90 规范,结果编译时提示错误信息: C++ style comments are not allowed in ISO C90
。
➜ gcc main.c -std=c90 main.c: In function ‘main’: main.c:7:36: error: C++ style comments are not allowed in ISO C90 printf("hello world!\n"); // 行注释 ^ main.c:7:36: error: (this will be reported only once per input file)
缺省情况下,GCC 默认使用的是 -std=gnu11
规范,即带携带 GNU 扩展的 C11 标准。
GNU 项目调试器
GNU 项目调试器( GDB , GNU Project Debugger )是一款可以调试 Ada、汇编、C\C++、D、Fortran、Go、Objective-C、OpenCL、Modula-2、Pascal、Rust 等多种语言的跨平台程序调试工具。为了捕获程序中的各类 Bug,GNU 项目调试器可以胜任下面 4 方面的工作:
- 启动程序,并指定能够影响其行为的任意内容。
- 在指定条件下停止程序的执行。
- 当程序停止时,检查发生了什么问题。
- 通过修改程序中的内容,从而尝试修复 bug。
向 Linux 命令控制台键入 gdb
即可运行 GDB 调试程序
➜ gdb GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git Copyright (C) 2018 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word". (gdb) |
进入 GDB 之后直接输入 help
即可以获取各类型命令的使用帮助,如果需要进一步查看指定类型命令的帮助则可以键入相应的命令分类,例如 help data
。
(gdb) help List of classes of commands: aliases -- Aliases of other commands breakpoints -- Making program stop at certain points data -- Examining data files -- Specifying and examining files internals -- Maintenance commands obscure -- Obscure features running -- Running the program stack -- Examining the stack status -- Status inquiries support -- Support facilities tracepoints -- Tracing of program execution without stopping the program user-defined -- User-defined commands Type "help" followed by a class name for a list of commands in that class. Type "help all" for the list of all commands. Type "help" followed by command name for full documentation. Type "apropos word" to search for commands related to "word". Command name abbreviations are allowed if unambiguous.
如果需要退出当前的 GDB 命令行调试界面,则可以输入 quit
,下面的表格列出了 GDB 当中常用的一些命令:
命令 | 描述 | 命令 | 描述 |
---|---|---|---|
break |
设置断点, break 断点所在行号 。 |
list |
列出产生执行文件的部分源码。 |
clear |
清除断点, clear 断点所在行号 。 |
next |
执行一行源码,但是不进入函数内部。 |
delete |
清除断点和自动显示的表达式 | step |
执行一行源码,并且进入函数内部。 |
disable |
使所设断点暂时失效,多个行号可用空格分隔。 | run |
正常执行当前被调试的程序。 |
enable |
生效所设的断点,与 disable 作用相反。 |
quit |
退出当前 GDB 命令行调试。 |
run |
运行调试程序。 | watch |
监视指定变量的值。 |
countinue |
继续执行正在调试的程序。 | make |
在 GDB 重新生成可执行文件。 |
file |
装载需要调试的可执行文件。 | shell |
在 GDB 当中执行 UNIX Shell 命令。 |
kill |
终止正在调试的程序。 | file |
加载可执行的文件。 |
提示:GDB 当中即可以像 Bash 或 Z-Shell 那样使用 Tab 键命令自动补齐,也能够通过方向键上下翻阅历史命令。
debug 范例
接下来,下面例程用于打印当前执行程序的名称以及命令行执行时所携带的参数,我们将会通过它来演示 GDB 调试程序的过程。
#include <stdio.h> int main(int argc, char *argv[]) { printf("当前执行程序的名称:%s\n", argv[0]); int index; for (index = 1; index < argc; index++) { printf("执行命令时输入的第%d个参数为:%s\n", index, argv[index]); } return 0; }
(1)首先需要使用 gcc -g main.c
命令编译程序并保留调试 debug 信息:
➜ gcc -g main.c ➜ ls a.out main.c
(2)进入 GDB 然后装载需要进行调试的可执行文件:
➜ gdb ... ... (gdb) file a.out Reading symbols from a.out...done.
(3)输入 GDB 的 run
命令,执行已经装载的 bugging 文件,并在命令后跟随需要传入程序的参数。
(gdb) run 这是一个Hank的测试程序! Starting program: /workspace/c-test/a.out 这是一个Hank的测试程序! 当前执行程序的名称:/workspace/c-test/a.out 执行命令时输入的第1个参数为:这是一个Hank的测试程序! [Inferior 1 (process 7997) exited normally]
(4)通过 where
命令,查看程序运行中出现的错误堆栈:
(gdb) where No stack.
(5)使用 list
命令查看当前执行程序的源码,每次能够查看 10 行,需要查看更多可以直接回车重新执行上一次输入的命令。
(gdb) list 1 #include <stdio.h> 2 3 int main(int argc, char *argv[]) { 4 printf("当前执行程序的名称:%s\n", argv[0]); 5 int index; 6 for (index = 1; index < argc; index++) { 7 printf("执行命令时输入的第%d个参数为:%s\n", index, argv[index]); 8 } 9 return 0; 10 }(gdb)
(6)利用 break
命令在程序的第 5 行位置设置一个断点:
(gdb) break 5 Breakpoint 1 at 0x555555554674: file main.c, line 5.
(7)重新输入 run
命令,此时程序会运行到第 5 行断点位置并停止:
(gdb) run Starting program: /workspace/c-test/a.out 这是一个Hank的测试程序! 当前执行程序的名称:/workspace/c-test/a.out Breakpoint 1, main (argc=2, argv=0x7fffffffded8) at main.c:6 6 for (index = 1; index < argc; index++) {
(8)输入 next
命令,在断点位置开始单步执行:
(gdb) next 7 printf("执行命令时输入的第%d个参数为:%s\n", index, argv[index]); (gdb) next 执行命令时输入的第1个参数为:这是一个Hank的测试程序! 6 for (index = 1; index < argc; index++) {
(9)断点执行过程中,可以使用 print
命令查看程序中指定变量的当前值:
(gdb) print index $1 = 1 (gdb) print argc $2 = 2 (gdb) print argv $3 = (char **) 0x7fffffffded8
(10)当发现程序状态出现错误的原因之后,就可以使用 kill
退出当前 debug 的程序,然后 quit
离开 GDB 调试器。
(gdb) kill Kill the program being debugged? (y or n) y (gdb) quit
TUI 模式
直接通过 GDB 命令行进行 debug 工作显然比较繁琐,因此 GDB 内置的 TUI ( TextUser Interface )模式提供了一套文本 UI 界面,能够方便的显示源码、汇编、寄存器的状态。可以直接通过 gcb -tui
命令直接进入 TUI 模式,或者在进入 GDB 命令行后使用 CTRL+X+A 快捷键进入。进入 TUI 模式后,GDB 窗口划分为 源代码查看 和 GDB 命令行 两个子窗口。
┌──main.c───────────────────────────────────────────────────────────────────┐ │1 #include <stdio.h> │ │2 │ │3 int main(int argc, char *argv[]) { │ │4 printf("当前执行程序的名称:%s\n", argv[0]); │ │5 int index; │ │6 for (index = 1; index < argc; index++) { │ │7 printf("执行命令时输入的第%d个参数为:%s\n", index, argv[index]); │ │8 } │ │9 return 0; │ │10 }^? │ │11 │ │12 │ │13 │ │14 │ └───────────────────────────────────────────────────────────────────────────┘ exec No process In: L?? PC: ?? Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from a.out...done. ---Type <return> to continue, or q <return> to quit---2 in /workspace/c-test/main.c (gdb) |
DDD 图形前端
DDD( Data Display Debugger ) 是一款简洁的 GDB 图形调试界面,Ubuntu 系统当中可以通过如下命令进行安装:
sudo apt-get install ddd
DDD 的使用与 TUI 类似,窗口依然被划分为 源代码查看 和 GDB 命令行 两个子窗口,同时右侧还增加了一个方便的操作面板。
注意:DDD 不支持中文注释,如果打开携带有中文注释的源代码,会导致这些代码在 DDD 窗口显示不完整。
以上所述就是小编给大家介绍的《嵌入式&桌面 Linux 下的 GCC与GDB 应用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机图形学与几何造型导论
戈德曼 / 邓建松 / 2011-9 / 69.00元
《世界著名计算机教材精选:计算机图形学与几何造型导论》是世界著名计算机教材精选之一。《世界著名计算机教材精选:计算机图形学与几何造型导论》共四部分三十章节,内容包括乌龟绘图,应用递归乌龟程序生成分形,分形的奇特性质,仿射变换,仿射几何:二维计算机图形学的连点过程,应用迭代函数系统生成分形,不动点定理及其推论,递归乌龟程序与共形迭代函数系统等。《世界著名计算机教材精选:计算机图形学与几何造型导论》可......一起来看看 《计算机图形学与几何造型导论》 这本书的介绍吧!