Linux下编译与调试.docx
《Linux下编译与调试.docx》由会员分享,可在线阅读,更多相关《Linux下编译与调试.docx(27页珍藏版)》请在冰豆网上搜索。
Linux下编译与调试
LINUX下编译与调试
1.gcc/g++编译器
对于.c格式的C文件,可以采用gcc或g++编译
对于.cc、.cpp格式的C++文件,应该采用g++进行编译
常用的选项:
-c表示编译源文件
-o表示输出目标文件
-g表示在目标文件中产生调试信息,用于gdb调试
-D<宏定义>编译时将宏定义传入进去
-Wall打开所有类型的警告。
1.gcc编译过程:
预编译编译汇编链接
当我们进行编译的时候,要使用一系列的工具,我们称之为工具链.其中包括:
预处理器,编译,汇编器as,连接器.一个编译过程包括下面几个阶段:
(1)预处理:
预处理器将对源文件中的宏进行展开。
(2)编译:
gcc将c文件编译成汇编文件。
(3)汇编:
as将汇编文件编译成机器码。
(4)链接:
将目标文件和外部符号进行连接,得到一个可执行二进制文件。
下面以一个很简单的test.c来探讨这个过程。
#include
#defineNUMBER(1+2)
intmain()
{
intx=NUMBER;
return0;
}
(1)预处理:
gcc–Etest.c-otest.i我们用cat查看test.i的内容如下:
intmain()intx=(1+2);return0;我们可以看到,文件中宏定义NUMBER出现的位置被(1+2)替换掉了,其它的内容保持不变。
(2)编译:
gcc-Stest.i–otest.s通过cattest.s查看test.s的内容为汇编代码。
(3)汇编:
astest.s-otest.o利用as将汇编文件编译成机器码。
得到输出文件为test.o.test.o中为目标机器上的二进制文件.用nm查看文件中的符号:
nmtest.o输出如下:
00000000Tmain。
有的编译器上会显示:
00000000b.bss00000000d.data00000000t.textU___mainU__alloca00000000T_main既然已经是二进制目标文件了,能不能执行呢?
试一下./test.o,提示cannotexecutebinaryfile.原来___main前面的U表示这个符号的地址还没有定下来,T表示这个符号属于代码。
(4)链接:
gcc–otesttest.o,将所有的.o文件链接起来生产可执行程序。
2.gcc所支持后缀名:
3.gcc常用选项:
预处理阶段:
对包含的头文件(#include)和宏定义(#define、#ifdef等)进行处理
gcc–Ehello.c–ohello.i//-o表示输出为指定文件类型-E将源文件(*.c)转换为(*.i)
编译阶段:
检查代码规范性、语法错误等,在检查无误后把代码翻译成汇编语言
gcc–Shello.i–ohello.s//-S将已预处理的C原始程序(*.i)转换为(*.s)
链接阶段:
将.s的文件以及库文件整合起来链接为可执行程序
gcc–ohello.exehello.s//最后将汇编语言原始程序(*.s)和一些库函数整合成(*.exe)
Example1:
#include
#defineMAX100
#definemax(a,b)((a)>(b)?
(a):
(b))//宏定义,执行-E之后被替换
main()
{
printf("MAX=%d\n",MAX);
printf("max(3,4)=%d\n",max(3,4));
}
//法一:
gcc–Eproject1.c–oproject1.i//预编译,生成已预编译过的C原始程序*.i
gcc–Sproject1.i–oproject1.s//编译,生成汇编语言原始程序*.s
gcc–oproject1.exeproject1.s//链接,生成可执行程序
//法二:
gcc–cproject1.c–oproject1.o//编译
gcc–oproject1.exeproject1.o//链接
//法三:
gcc–oproject1.exeproject1.c//编译并链接
Example2:
#include
main()
{
#ifdefcjy//表示如果定义了cjy,即命令行参数传了cjy,就执行下面的输出
printf("cjyisdefined!
\n");
#else
printf("cjyisnotdefined!
\n");
#endif
printf("mainexit\n");
}
gcc–Eproject2.c–oproject2.i–Dcjy//条件编译,用-D传递,如果没有传cjy则执行#else
gcc–Sproject2.i–oproject2.s
gcc–oproject2.exeproject2.s
或:
gcc–oproject2project2.c–Dcjy
4.gcc库选项
函数库分为静态库和动态库。
静态库是目标文件.o的归档文件(格式为libname.a)。
如果在编译某个程序时链接静态库,则链接器将会搜索静态库并直接拷贝到该程序的可执行二进制文件到当前文件中;
动态库(格式为libname.so[.主版本号.次版本号.发行号])。
在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入。
创建静态库
$gcc-ccalc.c//编译add.c源文件生成add.o目标文件
$arcrsvlibcalc.acalc.o//对目标文件*.o进行归档,生成lib*.a,此处lib要写
$gcc-omian.exemain.c-L./–libcalc–I.///不要忘记-L后面的那个.(即在库文件的搜索路径中添加当前路径-ladd表示链接库文件libadd.a/.so-I./表示包含在当前目录中的头文件)
$./main
创建动态库
$gcc-fPIC-Wall-cadd.c
$gcc-shared-olibadd.soadd.o
$gcc-omainmain.c-L.–ladd
在运行main前,需要注册动态库的路径。
方法有3种:
修改/etc/ld.so.conf或修改LD_LIBRARY_PATH环境变量或将库文件拷贝到/lib或者/usr/lib下(系统默认搜索库路径)。
$cplibadd.so/lib//通常采用的方法,cplib*.so/lib
$./main
如果不拷贝,生成.so之后还有两种方法:
1》gcc–omainmain.c–L.–Wl,-rpath,${PWD}–ladd
2》在main.c中修改如下:
#include
#include
#include"add.h"
#defineLIB_NAME"./libadd.so"
#defineFLAGSRTLD_NOW//表示现在就替换
intmain(intargc,char*argv[])
{
void*handle=NULL;
void(*func)(int,int);//typedefvoid(*func)(int,int);
//open
handle=dlopen(LIB_NAME,FLAGS);
if(NULL==handle)
{
printf("openerr!
\n");
return-1;
}
//find"my_printf"
func=dlsym(handle,"add");
//run
func(3,4);
//close
dlclose(handle);
return0;
}
最后执行:
gcc–omainmain.c-ldl
静态库与动态库的比较:
动态库只在执行时才被链接使用,不是直接编译为可执行文件,并且一个动态库可以被多个程序使用故可称为共享库。
可通过lld命令查看依赖的共享库。
静态库将会整合到程序中,在程序执行时不用加载静态库。
因此,静态库会使你的程序臃肿并且难以升级,但比较容易部署。
而动态库会使你的程序轻便易于升级但难以部署。
Example:
写一个求两个数+,--,*,/的函数func.c(func.h),在main.c中调用执行相应的算术操作,但是不直接针对main.c编译链接,而是在function.sh中对func函数创建静态库和动态库,并分别自动执行main函数。
(注:
当然可以直接针对main函数)
1.编写function.sh
#!
/bin/bash
echo"==========================="
echo"***************************"
echo"1.createstaticlib"//静态库创建
echo"2.createsharedlib"//动态库创建
echo"***************************"
echo"==========================="
echo"pleaseinputyouroperator:
"
readop
case$opin
"1")//以静态库的方式
gcc-c${1}.c//${1}接收第一个传进来的参数func,并编译它
arrcsvlib${1}.a${1}.o//将其打包为静态库
gcc-o${2}${2}.c-L.-l${1}//${2}接收第二个传进来的main
./${2}//运行main程序输出结果
;;
"2")//以动态库的方式
gcc-fpic-c${1}.c
gcc-shared-olib${1}.so${1}.o
gcc-o${2}${2}.c-L.-l${1}
sudocplib${1}.so/lib///切换到root用户下
./${2}
;;
*)
exit3
;;
esac
2.编写func.h
#ifndef__FUNC_H
#define__FUNC_H
externintadd(int,int);
externintsub(int,int);
externintmul(int,int);
externintdiv(int,int);
#endif
3.编写func.c
#include“func.h”
intadd(inta,intb)
{
returna+b;
}
intsub(inta,intb)
{
returna-b;
}
intmul(inta,intb)
{
returna*b;
}
intdiv(inta,intb)
{
returna/b;
}
4.编写main.c
#include
#include"func.h"
main()
{
printf("add(3,4)=%d\n",add(3,4));
printf("sub(4,1)=%d\n",sub(4,1));
printf("mul(3,2)=%d\n",mul(3,2));
printf("div(6,2)=%d\n",div(6,2));
}
#shfunction.shfuncmain//将func,main作为参数传递进去
当然也可以不用写function.sh,可以直接针对main操作,分别采用静态库和动态库的方式将函数func.c打包
1.将func.o打包为静态库函数libfunc.a,并执行程序:
#gcc–cfunc.c//1.将func.c编译为func.o
#arrcsvlibfunc.afunc.o//2.用arrcsv将func.o打包为静态库libfunc.a(前面的lib要写)
#gcc–omain.exemain.c–L.–lfunc//3.链接库函数和执行main.c生成可执行程序main.exe
#./main.exe//4.执行./main.exe
2.将func.o打包为动态库函数libfunc.so,并执行程序:
#gcc–fpic–cfunc.c//1.用动态库的方式将func.c编译为func.o
#gcc–shared–olibfunc.sofunc.o//2.用gcc-shared将func.o打包为动态库libfunc.so
#gcc–omain.exemain.c–L.–lfunc//3.链接库函数和执行main.c生成可执行程序main.exe
#sudocplibfunc.so/lib//4.非超级用户要用sudo将动态库libfunc.so拷贝到/lib目录下
#./main.exe//5.执行./main.exe
5.gcc---警告选项
对于如下程序:
#include
voidmain()
{
longlongtemp=1;
printf(“Thisisabadcode!
\n”);
return0;
}
●-ansi:
生成标准语法(ANSIC标准)所要求的警告信息(并不列出所有警告)
$gcc–ansiwarning.c–owarning
warning.c:
在函数“main”中:
warning.c:
7警告:
在无返回值的函数中,“return”带返回值
warning.c:
4警告:
“main”的返回类型不是“int”
可以看出,该选项并没有发现“longlong”这个无效数据类型的错误
●-pedantic:
列出ANSIC标准的全部警告信息。
$gcc–pedanticwarning.c–owarning
warning.c:
在函数“main”中:
warning.c:
5警告:
ISOC90不支持“longlong”
warning.c:
7警告:
在无返回值的函数中,“return”带返回值
warning.c:
4警告:
“main”的返回类型不是“int”
●-Wall:
列出所有的警告信息(常用)
$gcc–Wallwarning.c–owarning
warning.c:
4警告:
“main”的返回类型不是“int”
warning.c:
在函数“main”中:
warning.c:
7警告:
在无返回值的函数中,“return”带返回值
warning.c:
5警告:
未使用的变量“tmp”
$gcc–Werrorwarning.c–owarming
通常用的是-Wall显示所有有用的报警信息。
6.gcc---优化选项
gcc对代码进行优化通过选项“-On”来控制优化级别(n是整数)。
不同的优化级别对应不同的优化处理工作。
如使用优化选项“-O1”主要进行线程跳转和延迟退栈两种优化。
使用优化选项“-O2”除了完成所有“-O1”级别的优化之外,还要进行一些额外的调整工作,如处理其指令调度等。
选项“-O3”则还包括循环展开或其他一些与处理器特性相关的优化工作。
虽然优化选项可以加速代码的运行速度,但对于调试而言将是一个很大的挑战。
因为代码在经过优化之后,原先在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方,循环语句也有可能因为循环展开而变得到处都有,所有这些对调试来讲都是不好的。
所以在调试的时候最好不要使用任何的优化选项,只有当程序在最终发行的时候才考虑对其进行优化。
通常用的是-O2
-D<宏定义>编译时将宏定义传入进去
eg:
#gcc–ohello–Wall–O2hello.c
例:
有两个文件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//C++文件类型的,如.cpp的一定要用g++
将编译func.cpp,并且生成同名的但扩展名为.o的二进制目标文件func.o
g++-cmain.cpp
将编译main.cpp,并且生成同名的但扩展名为.o的二进制目标文件main.o
2、g++-cfunc.cpp-ofunc.o
g++-cmain.cpp–omain.o
编译main.cpp,并输出目标文件main.o
3、链接
g++main.ofunc.o//默认情况下生成的是a.out可执行文件
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(不加任何参数表示编译并链接生成可执行文件a.out)
g++-oa.outfunc.cppmain.cpp
都将先编译指定的源文件,如果成功的话,再链接成可执行文件a.out
5、如果希望在编译时传入宏定义,可使用-D参数,例如
g++*.cpp-D_DEBUG
1.1.make工程管理器
可以试想一下,有一个上百个文件的代码构成的项目,如果其中只有一个活少数几个文件进行了修改,如果再从头到尾将每一个文件都重新编译是个比较繁琐的过程。
为此,引入了Make工程管理器的概念,工程管理器指管理较多的文件,它是自动管理器能根据文件时间自动发现更新过的文件而减少编译的工作量,同时通过读入Makefile文件来执行大量的编译工作
makefile格式
target:
dependency_files//目标项:
依赖项
command//必须以tab开头,command编译命令
注意点:
在写command命令行的时候,必须要在前面按TAB键
例如,有makefile文件,内容如下:
使用make编译
对于该makefile文件,程序make处理过程如下:
●make程序首先读到第1行的目标文件main.exe和它的两个依赖文件main.o和func.o;然后比较文件main.exe和main.o/func.o的产生时间,如果main.exe比main.o/func.o旧的话,则执行第2条命令,以产生目标文件main.exe。
●在执行第2行的命令前,它首先会查看makefile中的其他定义,看有没有以第1行main.o和func.o为目标文件的依赖文件,如果有的话,继续按照
(1)、
(2)的方式匹配下去。
●根据
(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
●目标文件func.o按照上面的同样方式判断产生.
●执行(3)、(4)产生完main.o和func.o以后,则第2行的命令可以顺利地执行了,最终产生了第1行的目标文件main.exe。
1.2.特殊处理与伪目标
.PHONY是makefile文件的关键字,表示它后面列表中的目标均为伪目标
.PHONY:
b
b:
echo‘b’//通常用@echo“hello”
伪目标通常用在清理文件、强制重新编译等情况下。
Example1:
main.c函数,func.c函数为前面计算+,-,*,/运算的程序
#viMakefile//系统默认的文件名为Makefile
main.exe:
main.ofunc.o//表示要想生成main.exe文件,要依赖于main.o和func.o文件
gcc-omain.exemain.ofunc.o//如果main.o,func.o已经存在了,就链接成main.exe
main.o:
main.c//表示main.o文件依赖于main.c文件
gcc-cmain.c//编译main.c,默认生成main.o。
可写为:
gcc–cmain.c–omain.o
func.o:
func.c//表示func.o文件依赖于func.c文件
gcc-cfunc.c//如果func.c存在,则编译func.c,生成func.o
.PHONY:
rebuildclean//表示后面的是伪目标,通常用在清理文件、强制重新编译等情况下
rebuild:
cleanmain.exe//先执行清理,在执行main.exe
clean:
rm–rfmain.ofunc.omain.exe//最后删除.o和.exe的文件
按ESC键之后,:
wq保存退出
再执行下面的命令:
#make//直接make,即从默认文件名(Makefile)的第一行开始执行
#makeclean//表示执行clean:
开始的命令段
#makefunc.o//表示执行func.o:
开始的命令段
#makerebuild//则先执行清除,再重新编译连接
如果不用系统默认的文件名Makefile,而是用户随便起的一个名字,如:
#viMakefile11
则make后面必须要加上-fMakefile11,如:
#make–fMakefile11clean//表示执行clean:
开始的命令段
#make–fMakefile11main.exe//表示执行main.exe:
开始的命令段
1.3.变量、函数与规则
随着软件项目的变大、变复杂,源文件也越来越多,如果采用前面的方式写makefile文件,将会使makefile也变得复杂而难于维护。
通过make支持的变量定义、规