Linux下编译与调试.docx
- 文档编号:16711200
- 上传时间:2023-07-16
- 格式:DOCX
- 页数:27
- 大小:124KB
Linux下编译与调试.docx
《Linux下编译与调试.docx》由会员分享,可在线阅读,更多相关《Linux下编译与调试.docx(27页珍藏版)》请在冰点文库上搜索。
Linux下编译与调试
LINUX下编译与调试
课程内容
linux下C/C++的编译、链接、运行过程,gcc的使用,makefile的配置使用,gdb程序调试。
目录
1.gcc/g++编译器1
2.makefile使用3
2.1.基本过程处理3
2.2.特殊处理与伪目标4
2.3.变量、函数与规则5
3.程序调试8
3.1.gdb常用命令8
3.2.gdb应用举例9
3.3.assert断言宏13
3.4.错误处理函数和进程退出函数13
3.5.系统日志16
1.gcc/g++编译器
对于.c格式的C文件,可以采用gcc或g++编译
对于.cc、.cpp格式的C++文件,应该采用g++进行编译
常用的选项两种
-c表示编译源文件
-o表示输出目标文件
-g在目标文件中产生调试信息,用于gdb调试
-D<宏定义>编译时将宏定义传入进去
-Wall选项可以打开所有类型的语法警告,以便帮助我们确定代码是正确的,并且尽可能实现可移植性。
例如有两个文件main.cpp,func.cpp
其中
main.cpp内容为:
#include
intMyFunc();
intmain()
{
#ifdef_DEBUG
printf("DebugMyFuncis:
%d\n",MyFunc());
#else
printf("NDEBUGMyFuncis:
%d\n",MyFunc());
#endif
}
func.cpp内容为:
intMyFunc()
{
return123;
}
编译和连接的方式有如下一些:
1、g++-cfunc.cpp
将编译func.cpp,并且生成同名的但扩展名为.o的二进制目标文件func.o
同样道理
g++-cmain.cpp
将编译main.cpp,并且生成同名的但扩展名为.o的二进制目标文件main.o
2、g++-cfunc.cpp-ofunc.o
功能同
(1)一样,但是显式地指定了输出文件名为main.o
同样道理
g++-cmain.cpp–omain.o
编译main.cpp,并输出目标文件main.o
3、
(1)、
(2)的基础上
●g++main.ofunc.o
●g++-oa.outmain.ofunc.o
●g++-oa.out*.o
都将连接目标文件main.o和func.o最后形成可执行文件a.out
对于第一种,如果没有显式指定可执行文件名,g++默认为a.out
4、也可以将编译和链接的过程合为一块处理:
g++*.cpp
g++func.cppmain.cpp
g++-oa.outfunc.cppmain.cpp
都将先编译指定的源文件,如果成功的话,再链接成可执行文件a.out
采用第4种方式,在其中第某个源文件中必须有main函数,否则链接通不过。
5、如果希望在编译时传入宏定义,可使用-D参数,例如
g++-D_DEBUG*.cpp
2.makefile使用
2.1.基本过程处理
makefile的工作过程为:
先将需要编译连接的c/c++源文件组织到文件makefile中,
接着运行make程序,make程序读取当前文件夹下面的makefile文件信息,并根据makefile
里面的组织信息,调用相应的gcc/g++/shell等程序,完成对源文件的批量编译和连接。
要写makefile文件,首页必须清楚目标文件和依赖文件的概念。
通常情况下,目标文件和依赖文件都是指实际的文件。
例如,有makefile文件,内容如下:
文件第一行中的文件main.exe称为目标文件,冒号后面以空格分隔的两个文件
称为main.exe的依赖文件。
意思是文件main.exe的产生依赖于文件main.o和
func.o
同样道理:
第3行的main.o为目标文件,main.cpp为main.o的依赖文件
第5行的func.o为目标文件,func.cpp为func.o的依赖文件.
文件第2行(以tab开头)表示要产生第1行的目标文件需要执行的命令。
对于该makefile文件,程序make处理过程如下:
1、make程序首先读到第1行的目标文件main.exe和它的两个依赖文件main.o和func.o;然后比较文件main.exe和main.o/func.o的产生时间,如果main.exe比main.o/func.o旧的话,则执行第2条命令,以产生目标文件main.exe。
2、在执行第2行的命令前,它首先会查看makefile中的其他定义,看有没有以第1行main.o和func.o为目标文件的依赖文件,如果有的话,继续按照
(1)、
(2)的方式匹配下去。
3、根据
(2)的匹配过程,make程序发现第3行有目标文件main.o依赖于main.cpp,则比较目main.o与它的依赖文件main.cpp的文件新旧,如果main.o比main.cpp旧,则执行第4行的命令以产生目标文件main.o.在执行第4条命令时,main.cpp在文件makefile不再有依赖文件第定义,make程序不再继续往下匹配,而是执行第4条命令,产生目标文件main.o
4、目标文件func.o按照上面的同样方式判断产生.
执行(3)、(4)产生完main.o和func.o以后,则第2行的命令可以顺利地执行了,最终产生了第1行的目标文件main.exe。
2.2.特殊处理与伪目标
先看一些makefile的特殊情况:
makefile文件内容为
执行make时
如果文件a存在,echo‘a’将不会被调用,可以理解为文件a没有依赖文件,则认为文件a总是最新的,不需要执行;
如果文件a不存在,echo‘a’将会被调用。
如果文件b不存在,不管a是否存在,make时都将会报错
如果文件b存在,文件a不存在,则echo‘a’会被调用
如果文件a、b都存在,则按照正常的方式先比较文件新旧,决定是否调用echo
makefile里面有2组定义:
make时,如果文件a不存在,echo‘a’将会被调用,但是echo’b’不会被调用,说明make执行的入口点只有一个,就是第一个目标文件的定义,其它的调用都是由于有与第一条定义直接或间接依赖关系而被调用的;
makeb时,表示指定入口点为b,则echo‘b’被调用,echo‘a’不被调用,也说明入口点只有一个
makeb时,调用顺序为echo‘a’然后是echo‘b’,说明依赖体的匹配过程是全文件匹配,并不是顺序往下的。
伪目标:
再回到
(2)中的第一种情况,如果希望指定入口点为b,并且不管文件b是否都存在,都调用echo‘b’,则可以定义b为伪目标:
.PHONY是makefile文件的关键字,表示它后面列表中的目标均为伪目标,这样,不论文件b是否存在,执行makeb时,echo‘b’都将被调用。
伪目标通常用在清理文件、强制重新编译等情况下,例如:
执行makeclean将清除掉文件夹中的二进制可执行文件
执行makerebuild则先执行清除,再重新编译连接。
2.3.变量、函数与规则
随着软件项目的变大、变复杂,源文件也越来越多,如果采用前面的方式写makefile文件,将会使makefile也变得复杂而难于维护。
通过make支持的变量定义、使用、内置函数和规则,可以写出通用性较强的makefile文件,使得同一个makefile文件能够适应不能的项目。
变量:
为一文本串定义一个名字,名字即为变量的名称,文本串即为变量的值。
定义变量的一般方法:
变量名=变量值递规变量展开(几个变量共享一个值)
或者
变量名:
=变量值简单变量展开(类似于C++的赋值)
使用变量的一般方法:
$(变量名)=?
?
?
赋值
?
?
?
=$(变量名)引用
例:
make内部事先定义好了一些变量,他们分为两种类型,自动变量和预定义变量:
自动变量:
指在使用的时候,自动用特定的值替换。
常用的有:
变量
说明
$@
当前规则的目标文件
$<
当前规则的第一个依赖文件
$^
当前规则的所有依赖文件,以逗号分隔
$?
规则中日期新于目标文件的所有相关文件列表,逗号分隔
$(@D)
目标文件的目录名部分
$(@F)
目标文件的文件名部分
预定义变量:
也是make内部事先定义好的变量,但是它的值是固定的,并且有些的值是为空的。
常用的有:
变量
说明
$(CC)
C编译程序,默认值:
cc
$(CPP)
C预处理程序,默认值:
cpp
$(RM)
文件删除程序,默认值:
”rm–f”
$(CPPFLAGS)
传给C预处理程序第标志,没有默认值
$(CFLAGS)
传给C编译器第标志,没有默认值
根据内部变量,可以将makefile改写为:
模式规则:
模式规则是指通用第匹配方式,模式规则必须指定”%”,百分号可以匹配任何字符串,
例如下面规则:
%.o:
%.cpp
表示任何目标文件的依赖文件是与目标文件同名的并且扩展名为.cpp的文件
根据模式规则,上面的makefile可改写为:
函数:
makefile里的函数跟它的变量很相似——使用的时候,你用一个$符号跟开括号,函数名,空格后跟一列由逗号分隔的参数(第一个参数前不要逗号),最后用关括号结束。
例如,有一个叫wildcard的函数,它有一个参数,功能是搜索当前目录下的文件名,展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。
你可以像下面所示使用这个命令:
SOURCES=$(wildcard*.cpp)
这行会产生一个所有以'.cpp'的文件的列表,然后存入变量SOURCES里。
当然你不需要一定要把结果存入一个变量。
另一个有用的函数是字符串替换函数:
$(patsubst,要查找的子串,替换后的目标子串,源字符串)
它用于将源字符串(以空格分隔)中的所有要查找的子串替换成目标子串。
例如,处理那个经过上面定义后的变量,
OBJS=$(patsubst%.cpp,%.o,$(SOURCES))
将处理所有在SOURCES字列中的字(一列文件名),如果它的结尾是'.cpp',就用'.o'把'.cpp'替换。
注意这里的%符号将匹配一个或多个字符。
$(addprefix前缀,源字符串)函数把第二个参数列表的每一项前缀上第一个参数值
下面是一个较为通用的makefile:
3.程序调试
3.1.gdb常用命令
Linux包含了一个叫gdb的调试程序。
gdb可以用来调试C和C++程序。
gdb提供如下一些常用功能:
●它使你能监视你程序中变量的值.
●它使你能设置断点以使程序在指定的代码行上停止执行.
●它使你能一行行的执行你的代码.
为了使gdb正常工作,你必须使你的程序在编译时包含调试信息.调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。
gdb利用这些信息使源代码和机器码相关联。
在程序编译时用-g选项可打开调试选项.
gdb基本命令
gdb支持很多的命令使你能实现不同的功能.这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令,表1列出了你在用gdb调试时会用到的一些命令.
表1.基本gdb命令.
命令
描述
file
装入想要调试的可执行文件
kill
终止正在调试的程序
list
列出产生执行文件的源代码的一部分
例:
l
l3
l5,9
lfunc.cpp:
2,5
next
执行一行源代码但不进入函数内部
step
执行一行源代码而且进入函数内部
run
执行当前被调试的程序
quit
终止gdb
watch
使你能监视一个变量的值而不管它何时被改变
break
在代码里设置断点,这将使程序执行到这里时被挂起
例如:
make
使你能不退出gdb就可以重新产生可执行文件
shell
使你能不离开gdb就执行UNIXshell命令
whatis
显示变量的简单描述
ptype
显示变量的定义
backtrace(where)
显示函数调用堆栈
up
进入上函数调用堆栈的上一级
down
进入下函数调用堆栈的下以及
setvariable
临时定义一个变量语法为:
setvariablevarname=value
gdb支持很多与LinuxShell一样的命令编辑特征.你能象在bash或tcsh里那样按Tab键让gdb帮你补齐一个惟一的命令,如果不惟一的话gdb会列出所有匹配的命令.你也能用光标键上下翻动历史命令.
3.2.gdb应用举例
下面列出了将被调试的程序.这个程序有两个源文件main.cpp和func.cpp,它显示一个简单的问候,再用反序将它列出.
main.cpp:
voidMyPrint(constchar*pszSrc);
voidMyPrint2(constchar*pszSrc);
intmain()
{
charszSrc[]="hellothere";
MyPrint(szSrc);
MyPrint2(szSrc);
}
func.cpp
#include
#include
#include
voidMyPrint(constchar*pszSrc)
{
printf("Thestringis%s\n",pszSrc);
}
voidMyPrint2(constchar*pszSrc)
{
char*pszRev;
inti,iLen;
iLen=strlen(pszSrc);
pszRev=(char*)malloc(iLen+1);
for(i=0;i pszRev[iLen-i]=pszSrc[i]; pszRev[iLen]='\0'; printf("Therevertstringis: %s\n",pszRev); free(pszRev); } 用下面的命令编译它(注意加上-g的编译选项): g++-g*.cpp 这个程序执行时显示如下结果: Thestringishellothere Therevertstringis: 输出的第一行是正确的,但第二行打印出的东西并不是我们所期望的.我们所设想的输出应该是: Thestringprintedbackwardiserehtolleh 由于某些原因,MyPrint2函数没有正常工作.让我们用gdb看看问题究竟出在哪儿, 先键入如下命令: gdba.out 注意: 记得在编译a.out程序时把调试选项打开. 如果你在输入命令时忘了把要调试的程序作为参数传给gdb,你可以在gdb提示符下用file命令来载入它: (gdb)filea.out 这个命令将载入a.out可执行文件就象你在gdb命令行里装入它一样. 这时你能用gdb的run命令来运行a.out了.当它在gdb里被运行后结果大约会象这样: (gdb)filea.out Readingsymbolsfroma.out...done. (gdb)run Startingprogram: /home/ghaha/projects/testgdb/a.out Thestringishellothere Therevertstringis: Programexitednormally. 这个输出和在gdb外面运行的结果一样.问题是,为什么反序打印没有工作? 为了找出症结所在,我们可以在MyPrint2函数的for语句后设一个断点,具体的做法是在gdb提示符下键入list命令三次,列出源代码: (gdb)l[ist]func.cpp: 1 (gdb)l[ist] 技巧: 在gdb提示符下按回车健将重复上一个命令. 第一次键入l[ist]func.cpp: 1命令的输出如下: (gdb)lfunc.cpp: 1 1#include 2#include 3#include 4 5voidMyPrint(constchar*pszSrc) 6{ 7printf("Thestringis%s\n",pszSrc); 8} 9 10voidMyPrint2(constchar*pszSrc) 如果按下回车,gdb将再执行一次l[ist]命令,给出下列输出: (gdb)l 11{ 12char*pszRev; 13inti,iLen; 14iLen=strlen(pszSrc); 15pszRev=(char*)malloc(iLen+1); 16for(i=0;i 17pszRev[iLen-i]=pszSrc[i]; 18pszRev[iLen]='\0'; 19printf("Therevertstringis: %s\n",pszRev); 20free(pszRev);20 根据列出的源程序,你能看到要设断点的地方在第24行,在gdb命令行提示符下键入如下命令设置断点: (gdb)break17 gdb将作出如下的响应: (gdb)b17 Breakpoint1at0x80484fb: filefunc.cpp,line17. 现在再键入run命令,将产生如下的输出: (gdb)run Startingprogram: /home/ghaha/projects/testgdb/a.out Thestringishellothere Breakpoint1,MyPrint2(charconst*)(pszSrc=0xbfffe500"hellothere") atfunc.cpp: 17 17pszRev[iLen-i]=pszSrc[i]; 你能通过设置一个观察pszRev[iLen-i]变量的值的观察点来看出错误是怎样产生的,做法是键入: (gdb)watchpszRev[iLen-i] gdb将作出如下回应: Watchpoint2: string2[iLen-i] 现在可以用n[ext]命令来一步步的执行for循环了: (gdb)n 经过第一次循环后,gdb告诉我们pszRev[iLen-i]的值是`h`.gdb用如下的显示来告诉你这个信息: (gdb)n Watchpoint2: pszRev[iLen-i] Oldvalue=0'\0' Newvalue=104'h' MyPrint2(charconst*)(pszSrc=0xbfffe500"hellothere") atfunc.cpp: 16 16for(i=0;i 这个值正是期望的.后来的数次循环的结果都是正确的. 此时如果想一次运行到循环退出时,可以这么打断点: 首先取消查看和取消原来所有断点: (gdb)infob[查看所有断点信息] (gdb)db Deleteallbreakpoints? (yorn)y[删除原来所有断点] (gdb)b17ifi>=iLen-1[只有在i>=iLen-1时才停止在17行] (gdb)continue[继续全速运行] Continuing. Breakpoint2,MyPrint2(charconst*)(pszSrc=0xbfffde00"hellothere") atfunc.cpp: 17 17pszRev[iLen-i]=pszSrc[i]; (gdb)pi $1=10 (gdb)piLen $2=11 可以看到最后一个循环的赋值是针对于数组元素pszRev[1],pszRev[0]始终没能够赋值,如果pszRev[0]没有显式地赋值,并且缺省值为0的话,pszRev将表示一个空字符串。 3.3.assert断言宏 宏assert的原型定义在头文件 原型定义如下: #include voidassert(intiCondition); assert用在调试版本中,如果在源文件中定义了NDEBUG,assert宏和它里面的参数表达式都将不会执行 例如 #defineNDEBUG #include intmain() { assert(0);//将不会被执行 return0; } 或者在编译的时候,加入-DNDEBUG选项,也可以使assert无效。 3.4.错误处理函数和进程退出函数 错误号全局变量errno 通常errno为0,表示没有错误,Linux系统调用和许多库函数在出错时会将errno置为一个非0的值。 为了保证判断错误号不受干扰,最后在调用可能出错的函数前将全局变量errno置0。 例: #include #include #include #include #include intmain(intargc,char*argv[]) { errno=0; doublefSqrt=sqrt((double)atoi(argv[1])); if(errno) printf("sqrterror\n"); else printf("resultis: %f\n",fSqrt); return0; } strerror函数和perror函数 strerror根据错误号返回错误的描述, 原型: #include char*strerror(intiErrCode) pe
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux 编译 调试
![提示](https://static.bingdoc.com/images/bang_tan.gif)