工欲善其事必先利其器,本文就Linux底下C/C++开发工具的使用作简要的记录。主要包括编译工具gcc、调试工具gdb、make(Makefile)工具。
通常所说的GCC是GUN Compiler Collection的简称,除了编译程序之外,它还含其他相关工具,所以它能把易于人类使用的高级语言编写的源代码构建成计算机能够直接执行的二进制代码。GCC是Linux平台下最常用的编译程序,它是Linux平台编译器的事实标准。同时,在Linux平台下的嵌入式开发领域,GCC也是用得最普遍的一种编译器。GCC之所以被广泛采用,是因为它能支持各种不同的目标体系结构。例如,它既支持基于宿主的开发(简单讲就是要为某平台编译程序,就在该平台上编译),也支持交叉编译(即在A平台上编译的程序是供平台B使用的)。目前,GCC支持的体系结构有四十余种,常见的有X86系列、Arm、PowerPC等。
gcc命令提供了非常多的命令选项,但并不是所有都要熟悉,初学时掌握几个常用的就可以了,到后面再慢慢学习其它选项。
假设源程序文件名为test.c。
1.无选项编译链接
用法:#gcc test.c
作用:将test.c预处理、汇编、编译并链接形成可执行文件。这里未指定输出文件,默认输出为a.out。
2.选项-o
用法:#gcc test.c -o test
作用:将test.c预处理、汇编、编译并链接形成可执行文件test。-o选项用来指定输出文件的文件名。
3.选项-E
用法:#gcc -E test.c -o test.i
作用:将test.c预处理输出test.i文件。
4.选项-S
用法:#gcc -S test.i
作用:将预处理输出文件test.i汇编成test.s文件。
5.选项-c
用法:#gcc -c test.s
作用:将汇编输出文件test.s编译输出test.o文件。
6.无选项链接
用法:#gcc test.o -o test
作用:将编译输出文件test.o链接成最终可执行文件test。
7.选项-O
用法:#gcc-O1 test.c -o test
作用:使用编译优化级别1编译程序。级别为1~3,级别越大优化效果越好,但编译时间越长。
8选项 –Wall
gcc -Wall test.c -o test
编译test.c时给出警告信息。
GCC给出的警告信息虽然从严格意义上说不能算作错误,但却很可能成为错误的栖身之所。一个优秀的Linux程序员应该尽量避免产生警告信息,使自己的代码始终保持标准、健壮的特性。所以将警告信息当成编码错误来对待,是一种值得赞扬的行为!
9选项 –g
gcc -g test.c -o test
-g 产生一张用于调试和排错的扩展符号表。-g选项使程序可以用GNU的调试程序GDB进行调试。优化和调试通常不兼容,同时使用-g和-O(-O2)选项经常会使程序产生奇怪的运行结果。所以不要同时使用-g和-O(-O2)选项。
10
-Idir 将dir目录加到搜寻头文件的目录列表中去,并优先于在gcc缺省的搜索目录。在有多个-I选项的情况下,按命令行上-I选项的前后顺序搜索。dir可使用相对路径,如-I../inc等。
-Ldir 将dir目录加到搜寻-l选项指定的函数库文件的目录列表中去,并优先于gcc缺省的搜索目录。在有多个-L选项的情况下,按命令行上-L选项的前后顺序搜索。dir可使用相对路径。如-L../lib等。
-lname 在连接时使用函数库libname.a,连接程序在-Ldir选项指定的目录下和/lib,/usr/lib目录下寻找该库文件。在没有使用-static选项时,如果发现共享函数库libname.so,则使用libname.so进行动态连接。
-static 禁止与共享函数库连接。
-shared 尽量与共享函数库连接.
如果有多个源文件,基本上有两种编译方法:
[假设有两个源文件为test1.c和test2.c]
1.多个文件一起编译
用法:#gcc test1.c test2.c -o test
作用:将test1.c和test2.c分别编译后链接成test可执行文件。
2.分别编译各个源文件,之后对编译后输出的目标文件链接。
用法:
#gcc-c test1.c //将test1.c编译成testfun1.o
#gcc-c test2.c //将test2.c编译成test2.o
#gcc-o test1.o test2.o -o test //将test1.o和test2.o链接成test
以上两种方法相比较,第一中方法编译时需要所有文件重新编译,而第二种方法可以只重新编译修改的文件,未修改的文件不用重新编译。
参考:http://www.cnblogs.com/zhangpengshou/p/3587751.html
http://www.cnblogs.com/ggjucheng/archive/2011/12/14/2287738.html
http://developer.51cto.com/art/200609/32317.htm
GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。
命令 |
描述 |
backtrace(或bt) |
查看各级函数调用及参数 |
finish |
连续运行到当前函数返回为止,然后停下来等待命令 |
frame(或f) 帧编号 |
选择栈帧 |
info(或i) locals |
查看当前栈帧局部变量的值 |
list(或l) |
列出源代码,接着上次的位置往下列,每次列10行 |
list 行号 |
列出从第几行开始的源代码 |
list 函数名 |
列出某个函数的源代码 |
next(或n) |
执行下一行语句 |
print(或p) |
打印表达式的值,通过表达式可以修改变量的值或者调用函数 |
quit(或q) |
退出gdb调试环境 |
set var |
修改变量的值 |
start |
开始执行程序,停在main函数第一行语句前面等待命令 |
step(或s) |
执行下一行语句,如果有函数调用则进入到函数中 |
一个示例:
编译生成执行文件:(Linux下)
gcc –g test.c –o test
使用GDB调试:
gdb test <---------- 启动GDB
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2)7.7.1
Copyright (C) 2014 Free SoftwareFoundation, Inc.
License GPLv3+: GNU GPL version 3 or later<http://gnu.org/licenses/gpl.html>
This is free software: you are free tochange and redistribute it.
There is NO WARRANTY, to the extentpermitted by law. Type "showcopying"
and "show warranty" for details.
This GDB was configured as"x86_64-linux-gnu".
Type "show configuration" forconfiguration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentationresources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search forcommands related to "word"...
Reading symbols from test...done.
(gdb) l <-------------------- l命令相当于list,从第一行开始例出原码。
6 for(i=0; i<n; i++){
7 sum += i;
8 }
9 return sum;
10 }
12 intmain()
13 {
14 inti;
15 longresult = 0;
(gdb) <-------------------- 直接回车表示,重复上一次命令
16 for(i=1;i<=100; i++){
17 result+= i;
18 }
20 printf("result[1-100]= %d /n", result );
21 printf("result[1-250]= %d /n", func(250) );
22 }
(gdb) break 16 <-------------------- 设置断点,在源程序第16行处。
Breakpoint 1 at 0x40056b: file test.c, line16.
(gdb) break func <-------------------- 设置断点,在函数func()入口处。
Breakpoint 2 at 0x400534: file test.c, line5.
(gdb) info break <-------------------- 查看断点信息。
Num Type Disp EnbAddress What
1 breakpoint keep y 0x000000000040056b in main at test.c:16
2 breakpoint keep y 0x0000000000400534 in func at test.c:5
(gdb) r <--------------------- 运行程序,run命令简写
Starting program: /home/hiil/apue/test
Breakpoint 1, main () at test.c:16
(gdb) n <--------------------- 单条语句执行,next命令简写。
18 for(i=1; i<=100; i++)
(gdb) n
17 result+= i;
(gdb) c <--------------------- 继续运行程序,continue命令简写。
Continuing.
Breakpoint 2, func (n=250) at test.c:5
5 int sum=0,i; 5 int sum=0,i;
(gdb) p i <--------------------- 打印变量i的值,print命令简写。
$1 = 32767
(gdb) bt <--------------------- 查看函数堆栈。
#0 func (n=250) at tst.c:5
#0 func (n=250) at test.c:6
#1 0x00000000004005a7 in main () at test.c:21
(gdb) finish <--------------------- 退出函数。
Run till exit from #0 func (n=250) at test.c:6
0x00000000004005a7 in main () at test.c:21
21 printf("result[1-250]= %d /n", func(250) );
Value returned is $2 = 31125
(gdb) q <--------------------- 退出gdb。
core dump又叫核心转储, 当程序运行过程中发生异常,程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump. (linux中如果内存越界会收到SIGSEGV信号,然后就会core dump)
在程序运行的过程中,有的时候我们会遇到Segment fault(段错误)这样的错误。这种看起来比较困难,因为没有任何的栈、trace信息输出。该种类型的错误往往与指针操作相关。往往可以通过这样的方式进行定位。
造成segment fault,产生core dump的可能原因
1.内存访问越界
a) 由于使用错误的下标,导致数组访问越界
b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符
c) 使用strcpy,strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy,strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。
2 多线程程序使用了线程不安全的函数。
3 多线程读写的数据未加锁保护。对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump
4 非法指针
a) 使用空指针
b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump.
5 堆栈溢出.不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。
配置操作系统使其产生core文件
首先通过ulimit命令查看一下系统是否配置支持了dump core的功能。通过ulimit -c或ulimit -a,可以查看core file大小的配置情况,如果为0,则表示系统关闭了dump core。可以通过ulimit -c unlimited来打开。若发生了段错误,但没有core dump,是由于系统禁止core文件的生成。
解决方法:
$ulimit -c unlimited (只对当前shell进程有效)
或在~/.bashrc 的最后加入: ulimit -c unlimited (一劳永逸)
# ulimit -c
0
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
用gdb查看core文件
发生core dump之后, 用gdb进行查看core文件的内容, 以定位文件中引发core dump的行.
gdb [exec file] [core file]
如: gdb ./test test.core
样例
1. 空指针
样例:
#include <stdio.h>
int main(void)
{
printf("hello world! dump core for set value to NULLpointer\n");
*(char *)0 = 0;
return 0;
}
# gcc -g test.c -o test
# ./test
hello world! dump core for set value toNULL pointer
Segmentation fault
# ls
test test.c
/* Set core file size to unlimited */
# ulimit -c unlimited
# ./test
hello world! dump core for set value toNULL pointer
Segmentation fault (core dumped)
/* Get core dump after change core filesize. */
# ls
core test test.c
/* gdb to debug core dump */
# gdb test core
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2)7.7.1
Copyright (C) 2014 Free SoftwareFoundation, Inc.
License GPLv3+: GNU GPL version 3 or later<http://gnu.org/licenses/gpl.html>
This is free software: you are free tochange and redistribute it.
There is NO WARRANTY, to the extentpermitted by law. Type "showcopying"
and "show warranty" for details.
This GDB was configured as"x86_64-linux-gnu".
Type "show configuration" forconfiguration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentationresources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search forcommands related to "word"...
Reading symbols from ./test...done.
[New LWP 5823]
Core was generated by `./test'.
Program terminated with signal SIGSEGV,Segmentation fault.
#0 0x0000000000400540 in main () at test.c:9
9 *(char *)0 = 0;
(gdb) bt
#0 0x0000000000400540 in main () at test.c:9
参考: http://blog.163.com/huang_bp/blog/static/123119837201003035231758/
http://blog.csdn.net/haoel/article/details/2879
使用GNU Make工具来管理程序是每个Linux工程师必须掌握的技能。Make能够使整个程序的编译、链接只需要一个命令(make)就可以完成。
Make的工作主要依赖于一个叫为Makefile的文件。Makefile文件描述了整个程序的编译,连接等规则。其中包括:工程中的哪些源文件需要编译以及如何编译,如何最后产生我们想要得可执行文件。
Makefile中最重要的组成部分是“规则”
规则:用于说明如何生成目标文件,规则的格式
如下:
targets : prerequisites
(table)command
目标: 依赖
命令
特别提供:命令需要使用【TAB】键空格
led.bin: led.o
arm-linux-ld-Tled.lds -o led.elf led.o
Makefile中把那些只包含命令,没有任何依赖的目标称为”伪目标”(phony targets).
.PHONY : clean
clean :
rm –f hello main.o func.o
“.PHONY”将“clean”目标声明为伪目标
1.当一个makefile中有多条规则时,如何单独执行某条规则?
2. 如果用户没有指定执行某一条规则,make会默认执行makefile中的第1条规则,而这条规则中的目标称之为:最终目标
变量
使用变量前:
app1: app1.o func1.o func2.o
gcc app1.ofunc1.o func2.o -o app1
app2: app2.o func1.o func2.o
gcc app2.ofunc1.o func2.o -o app2
使用变量后:
obj=func1.o func2.o
app1: app1.o $(obj)
gcc app1.o$(obj) -o app1
app2: app2.o $(obj)
gcc app2.o$(obj) -o app2
在makefile中,用户除了可以自己定义变量外,还可以使用存在系统已经定义好的默认变量。
$^:代表所有的依赖文件
$@:代表目标
$<:代表第一个依赖文件
Makefile规则-变量
使用前:
led.o : led.S
arm-linux-gcc -g–o led.o -c led.S
使用后:
led.o : led.S
arm-linux-gcc -g–o $@ -c $^
当一个makefile中有许多类似的规则时,如何将这些规则合并为一条通用规则?
%.o : %.S
arm-linux-gcc-g -c $^
%.o : %.c
arm-linux-gcc-g -c $^
两个技巧
Makefile中“#”字符后的内容被视作注释。
hello: hello.c
@gcc hello.c –o hello
@:取消回显
make命令默认在当前目录下寻找名字为makefile或者Makefile的工程文件,当名字不为这两者之一时,可以使用如下方法指定:
make –f 文件名
示例:
all: start.o arm-linux-ld -Tgboot.lds -o gboot.elf $^ arm-linux-objcopy -O binary gboot.elf gboot.bin %.o : %.S arm-linux-gcc -g -c $^ %.o : %.c arm-linux-gcc -g -c $^ .PHONY: clean clean: rm *.o *.elf *.bin |
更详细内容参见:GNU make中文手册和跟我一起写makefile (陈皓)
http://blog.csdn.net/haoel/article/details/2886/