C程序编译
gcc可接收处理多种文件,包括归档文件(.a),C语言源文件(.c),C++源文件(.C,.cc,.cxx),汇编语言源文件(.s),预处理输出文件(.i),目标代码(.o)
gcc [option] filelist
-c:只编译,不链接成可执行文件。编译器只是由输入的.c等为后缀的源代码文件生成.o为后缀的目标文件,通常用于编译不包含主程序的子程序文件。 -g:产生调试器gdb所必需的符号信息,要对源代码进行调试,就必须在编译程序时加入这个选项 -O[level]:对程序进行优化编译,链接,采用这个选项,整个源代码会在编译,链接过程中进行优化处理,这样产生的可执行文件的执行效率较高,但是,编译、链接的速度就相应地要慢一些。数字越大优化程度越高。 -o output_filename sourcename:确定输出文件的名称为output_filename,如果不用output_filename,默认输出文件为a.out -pg:产生供代码剖析工具gprof使用的信息 -S:跳过汇编和链接阶段,保留编译产生的汇编代码(.s文件) -v:产生尽可能多的输出信息 -w:忽略警告信息 -W:产生比默认模式更多的警告信息 -ansi:强制ANSI标准 -l libfile:链接库文件 -m typeName:根据CPU类型优化代码
需要编译的文件可以放在option前,也可放在option后
gcc f1 f2 ... fN -o outputfile
一般而言,文件数量多时,采取将各源文件编译为目标文件的操作先,然后再链接形成可执行文件,如:
gcc -c f1.c gcc -c f2.c ... gcc -c fN.c #也可写成gcc -c f1.c f2.c ... fN.c gcc f1.o f2.o ... fN.o outputfile
如此,当某个文件发生修改时,可只编译该文件,再将所有文件链接,节省时间。
使用程序库时,需要用-l命令链接,如链接math.h
gcc f1.c -lm -o outputfile
make:处理模块化c程序
make [options] [target] [Macro Definition] -h 或 --help:输出帮助信息。 -d:输出大量调试信息 -n:只打印需要执行的命令而不执行,测试模式 -s:是一个用于静默执行Makefile的命令选项。它会在执行过程中不输出任何信息,包括命令的执行结果和错误信息。 -j 或 --jobs:指定并行执行的任务数,即同时编译的文件数。 -I 或 --include-dir:指定头文件的搜索路径。 -f 或 --file:指定使用的Makefile文件,可以多次使用来指定多个文件。在文件名不是makefile或Makefile时需要使用此参数。 -k 或 --keep-going:即使遇到错误,也继续执行后续的编译任务。
make语法规则:
目标文件列表:依赖文件列表
<Tab>命令列表
目标文件列表中文件之间用空格隔开,依赖文件列表亦然。命令列表用回车隔开用户命令,每条命令都需要以<Tab>命令起头
如果想要强制编译系统,可以在make命令前执行touch命令
编写的makefile文件中各命令的执行顺序由编译过程决定,与makefile中的书写顺序无关。
make支持宏替换,需在makefine顶端定义宏:
makefile macro_name = text 或 define macro_name text endef
其中,macro_name
是您为宏指定的名称,而text
是宏所代表的文本片段或命令。引用宏时采用$(macro_name)
宏使用参考
$(TARGET): $(SRCS) gcc $(CFLAGS) -o $(TARGET) $(SRCS) $(LDFLAGS)
$(TARGET)
和$(SRCS)
分别代表目标文件名和源文件列表,$(CFLAGS)
和$(LDFLAGS)
分别代表编译选项和链接选项
CFLAGS默认值是-O(编译优化)
makefile文件中可以使用任意shell命令,如压缩等,可使用make command来调用,不过仍然按照上述的make规则语法书写例:
#marco_name definition CC = gcc OPTIONS = -O3 -o OBJECTS = f1.o f2.o f3.o SOURCES = f1.c f2.c f3.c HEADERS = f1.h f2.h f3.h objectfile : $(OBJECTS) $ (CC) $ (OPTIONS) objectfile $ (OBJECTS) -lm f1.o:$(HEADERS) ... all.tar:$(SOURCES) $(HEADERS) makefile tar -cvf - $(SOURCES) $(HEADERS) makefile >all.tar
调用make all.tar时,会执行将makefile本身以及编译过程中所涉及到的源文件,头文件打包的命令。而在make时不会执行。
常用的内建宏定义:
-
MAKE
:当前使用的make命令的路径。 -
MAKECMDGOALS
:当前make命令的参数。 -
MAKEFILE_LIST
:当前加载的Makefile的列表。 -
CURDIR
:当前目录的路径。 -
PREREQ
:所有先决条件的列表。 -
FIRST_PREREQ
:第一个先决条件。 -
DIR
:当前目录的名称。 -
NOTDIR
:去除目录路径后的文件名。 -
SUFFIX
:文件名的后缀。 -
BASENAME
:去除后缀后的文件名。 -
ADDSUFFIX
:给文件名添加后缀。 -
ADDPREFIX
:给文件名添加前缀。 -
JOIN
:将两个列表连接成一个。 -
WORDLIST
:从列表中提取单词。 -
FILTER
:过滤列表中的元素。 -
FINDSTRING
:查找字符串中的子串。 -
SORT
:对列表进行排序。 -
IF
、ELSE
、ENDIF
:条件判断语句。 -
foreach
、endforeach
:循环语句。 -
call
、endcall
:调用函数语句。
这些内建宏定义可以在Makefile中直接使用,无需额外的初始化。它们提供了一些常用的功能,使得Makefile的编写更加简单和灵活。
补充:
$@:比较当前目标文件的名字
$?:比当前目标文件新的依赖文件列表
$<:比当前目标文件新的第一个依赖文件
$^:用空格隔开所有依赖文件(重复出现的文件名只保留一个)
target: dependency gcc -o $@ $<
target
是目标文件名,dependency
是依赖文件名。当执行make target
命令时,Makefile会执行gcc -o $@ $<
命令来编译目标文件。
在这个命令中,$@
被替换为目标文件名target
,$<
被替换为依赖文件名dependency
在makefile中,以@开头的命令不会显示到屏幕上,除非使用了make -n
使用.PHONY
来声明这个目标是一个“假”目标,防止make
命令误解。
下面是一个简单的例子来说明.PHONY
的用途:
.PHONY: clean clean: rm -f *.o
在这个例子中,clean
是一个假目标,它并不表示一个实际存在的文件或目录。当执行make clean
命令时,make
命令会执行rm -f *.o
命令来删除所有的.o
文件,即使存在一个名为clean
的文件或目录,make clean
命令也会正确执行。
ar:创建、修改、释放库/归档文件
通常用于创建静态库,但也可以用于创建和提取其他类型的存档文件
ar
命令的基本语法如下:
ar [选项] [操作] [文件名]
文件名必须以.a结尾,创建后即可被C编译器和Linux库装载器ld所引用
选项可以不加连字符'-',又称为关键字
创建一个名为lib.a
的静态库,并将两个目标文件file1.o
和file2.o
添加到库中,可以使用以下命令:
ar rcs lib.a file1.o file2.o
这将创建一个名为lib.a
的存档文件,并将file1.o
和file2.o
添加到其中。-r
选项表示将文件替换现有的存档文件中,不存在则会创建,-c
选项表示创建一个新的存档文件(如果它不存在),-s
选项表示创建目标文件的索引,不需要创建索引时使用大写S参数,建立索引可提高目标模块的搜索速度并允许模块间函数互相调用,不管排列顺序如何
-
-x
:从存档文件中提取文件。ar x lib.a obj.o -
-d
:删除存档文件中的文件。 -
-m
:替换存档文件中的文件
q关键字可以将新的目标文件追加至归档文件的末尾
ar tv libxxx.a
t显示归档文件的内容,v显示详细信息
创建好归档文档后,可在gcc中链接,用法示例如下:
-
下命令编译和链接您的代码:
gcc main.c -L. -l:lib.a -o my_program
这里的-L.
告诉编译器在当前目录中查找库文件,-l:lib.a
指定要链接的库文件名称,-o my_program
指定生成的可执行文件的名称。
可以用`command```这一命令替换的方法获取批量目标文件
ar s lib.a 等价于ranlib lib.a
查看库文件信息
nm工具,查看库文件或目标文件符号表(符号名称、类型、大小、位置)
nm命令的基本语法如下:
nm [options] [file...]
常用的选项包括:
-
-a
:显示所有符号,包括隐藏的符号。 -
-C
:使用C语言的格式化方式显示符号。 -
-D
:显示动态符号。 -
-g
:显示外部符号。 -
-n
:以数字方式显示符号名。(按地址) -
-r
:以逆序方式显示符号列表。 -
-s
:显示符号大小。 -
-u
:显示未定义的符号。 -
-f
: 选择显示格式,后面可接sysv,bsd,posix等选项,默认为bsd
版本控制
RCS版本管理系统:以发布号.阶段号.分支号.序列号维护版本号
CVS并行版本系统:RCS的一个前端。允许多个开发者并行修改,提供冲突解决机制
静态分析工具
gprof工具,在使用前,gcc链接需要添加-pg参数(或-p),使得程序运行结束后生成的gmon.out文件可以供gprof分析
注:gprof默认不支持多线程程序,默认不支持共享库程序
gprof [option] [executable.file] [output.file]
-b 不再输出统计图表中每个字段的详细描述。
-p 只输出函数的调用图(Call graph的那部分信息)。
-q 只输出函数的时间消耗列表。
-e Name 不再输出函数Name 及其子函数的调用图(除非它们有未被限制的其它父函数)。可以给定多个 -e 标志。一个 -e 标志只能指定一个函数。
-E Name 不再输出函数Name 及其子函数的调用图,此标志类似于 -e 标志,但它在总时间和百分比时间的计算中排除了由函数Name 及其子函数所用的时间。
-f Name 输出函数Name 及其子函数的调用图。可以指定多个 -f 标志。一个 -f 标志只能指定一个函数。
-F Name 输出函数Name 及其子函数的调用图,它类似于 -f 标志,但它在总时间和百分比时间计算中仅使用所打印的例程的时间。可以指定多个 -F 标志。一个 -F 标志只能指定一个函数。-F 标志覆盖 -E 标志。
-z 显示使用次数为零的例程(按照调用计数和累积时间计算)。
gprof产生的信息
%time | Cumulativeseconds | Self Seconds | Calls | SelfTS/call | TotalTS/call | name |
---|---|---|---|---|---|---|
该函数消耗时间占程序所有时间百分比 | 程序的累积执行时间(只是包括gprof能够监控到的函数) | 该函数本身执行时间(所有被调用次数的合共时间) | 函数被调用次数 | 函数平均执行时间(不包括被调用时间)(函数的单次执行时间) | 函数平均执行时间(包括被调用时间) (函数的单次执行时间) | 函数名 |
Index | %time | Self | Children | Called | Name |
---|---|---|---|---|---|
索引值 | 函数消耗时间占所有时间百分比 | 函数本身执行时间 | 执行子函数所用时间 | 被调用次数 | 函数名 |
第一份报告包含的是函数列表,并按照执行时间排序(包含分支执行时间)
第二份报告显示程序的总运行时间,其中called项以“该函数被父函数调用的次数”/“该函数被调用的总次数”显示,函数的递归调用不包含在"/"后面的次数。name中包含函数名和索引号,若函数是循环,则会在函数名和索引号之间显示循环号。Index对应了函数的唯一索引号,与当前分析的函数位于同一行,在其上方(---以下)为其父函数,在其下方(---以上)为子函数
gprof只能分析应用程序在运行过程中消耗掉的用户时间,无法得到程序内核空间的运行时间,可用time看整个程序的运行时间组成。
动态分析工具
gdb
gdb启动程序用法
gdb bugging.c
启动时默认读入~/.gdbinit文件并执行里面的命令,可以用-n忽略此文件
-h:列出命令行的选项简要介绍
-q:禁止显示介绍信息和版权信息
-s[file]:使用保存在指定文件中的符号表
使用gdb前,使用gcc的-g参数编译程序以加入调试时所需要的符号表
同时,可以用nl -ba file.c为.c文件中的每一行前添加一个数字行号方便调试,-b表示添加行号,-a表示将行号显示为数字。
在gdb运行后使用的调试命令
-
run
:开始运行你的程序。 -
start
:开始执行程序。 -
n
:单步执行(next)。 -
finish
:执行完当前函数,返回并停在上个调用的函数。 -
quit
:退出gdb模式。 -
b
:在程序的第8行设置断点(8可以改成函数名,表示在函数处设置断点)。 -
c
:继续执行程序,程序会在设置断点处停下来。 -
display
:将b的值显示出来。 -
info
:查看一共设置了几个断点。 -
delete
:删除编号为3的断点。 -
watch
:当程序访问某个存储单元时中断 。 -
backtrace
:回溯程序发生错误时的位置 -
step
:单步执行,会深入函数内部 -
next
:单步执行,但不会进入函数内部 -
x
[addr]:查看addr处的内容 -
print
[str]:打印出变量或表达式得到值 -
list
:列出程序部分或全部源代码。后面可以接行号,文件名:行号,函数名,文件名:函数名,地址。
gdb调试时仍然允许执行外部shell命令。
调试完毕后,可以选用编译优化选项重新编译可执行文件,也可用strip file删除掉可执行文件中的调试信息。
time
time filename,详情可参考:
Linux time命令 | 菜鸟教程 (runoob.com)
显示格式为
real xxx
user xxx
sys xxx
real为实际时间,user为用户cpu时间(真正的代码执行时间),sys为系统cpu时间(系统活动所花费的时间)。
由于可能有多个进程同时运行,总时间并不一定等于系统时间与用户时间之和