求解:C++内联汇编申请内存访问内存地址的代码,在linux环境下用g++编译后出错?

linux下实现g++编译链接c++源文件和头文件_Linux_第七城市
linux下实现g++编译链接c++源文件和头文件
最基本的编译文件方法g++ -c xxx.cpp -o xxx./xxxC++编译多个文件&makefile&&逐步编译:g++ -c APCluster.cppAPCluster.h&//生成APCluster.o 中间文件g++ -c example.cppAPCluster.h&&//生成example.o中间文件g++ -o main APCluster.o example.o&makefile:GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,上面编译对应的makefile如下:main: APCluster.oexample.o&&&&&&&&//main为最终可执行文件&g++ -o main APCluster.o example.o //或者g++APCluster.o example.o-omain应该也行APCluster.o :APCluster.cppAPCluster.h&& //定义依赖关系&g++ -c APCluster.cppAPCluster.h&&&//如何生成目标文件的操作系统命令,一定要以一个tab键作为开头,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //-c只激活预处理,编译,和汇编,也就是他只把程序做成obj文件*.oexample.o: example.cpp APCluster.h&g++ -c example.cppAPCluster.h&&&&//APCluster.h可以不要,因为上句已经指明,会自动链接clean:&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//定义makeclean指令&rm main APCluster.o example.o我们可以把这个内容保存在名字为“makefile”或“Makefile”的文件中,然后在该目录下直接输入命令“make”就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。
最新教程周点击榜
微信扫一扫跪求大神指导:linux下使用gsoap生成C++代码访问WebService编译出错, 跪求大神指导:linux下使用gsoa
跪求大神指导:linux下使用gsoap生成C++代码访问WebService编译出错 并且在QT的pro文件里增加了如下语句,编译工程报一大堆类似如undefined reference to &quot小弟初次涉及WebService。;lib,java我知道可以.8的几个版本都报同一个错误;usr/XXXX/libgsoap++,想用C工程实现调用WebSimport成功生成了对应的文件:$LD_LIBRARY_PATH设置,实在没办法了,同时在QT编译环境里增加了LD_LIBRARY_PATH=&#47.使用linux下的gsoap工具使用了语句wsdl2h -s -o -soap_closesock&usr&#47.a;INCLUDEPATH+=gosop的import文件相关目录,利用QT建立了一个C++工程,不知道是不是这个原因,将生成的文件拷贝到对应的工程下;local&#47。别说用soapcpp2 -C test.h XXX;lib/XXX&#47。2.wsdl。跪求哈;gSOAP&#47。,跟踪.h文件发现好像出问题在soapdefs.h上.h -I /gSOAP&#47:LIBS+=-L /的问题:1。试过2;3;local&#47,现在就是需要用C或C++实现,求各位大神指导。 kanjiuchang 跪求大神指导:linux下使用gsoap生成C++代码访问WebService编译出错
和 stdsoap2同学我不知道为什么觉得你运气很好.h 添加到你的工程里面去...请把 stdsoap2Unix(23)
安装g++环境
安装两个RPM包即可搞定
[root@localhost&Desktop]#&rpm&-ivh&/home/weiwei/Desktop/libstdc++-devel-4.4.5-6.el6.i686.rpm&
[root@localhost&Desktop]#&rpm&-ivh&/home/weiwei/Desktop/gcc-c++-4.4.5-6.el6.i686.rpm
查看g++是否安装成功
[root@localhost&Desktop]#&g++&-v
Using&built-in&specs.
Target:&i686-redhat-linux
Configured&with:&../configure&--prefix=/usr&--mandir=/usr/share/man&--infodir=/usr/share/info&--with-bugurl=/bugzilla&--enable-bootstrap&--enable-shared&--enable-threads=posix&--enable-checking=release&--with-system-zlib&--enable-__cxa_atexit&--disable-libunwind-exceptions&--enable-gnu-unique-object&--enable-languages=c,c++,objc,obj-c++,java,fortran,ada&--enable-java-awt=gtk&--disable-dssi&--with-java-home=/usr/lib/jvm/java-1.5.0-gcj-1.5.0.0/jre&--enable-libgcj-multifile&--enable-java-maintainer-mode&--with-ecj-jar=/usr/share/java/eclipse-ecj.jar&--disable-libjava-multilib&--with-ppl&--with-cloog&--with-tune=generic&--with-arch=i686&--build=i686-redhat-linux
Thread&model:&posix
gcc&version&4.4.5&&(Red&Hat&4.4.5-6)&(GCC)
gcc与g++的区别
gcc可以用来编译C或者C++,但他只能编译c++源文件,不能自动和C++程序使用的库连接,g++可以实现C++程序的编译和链接,其实他也是调用gcc来编译的,要编译c++代码生成可执行文件要用 g++
编写一个简单的c++程序
//&myfirst.cpp--displays&a&message
#include&&iostream&&&&&&&//&make&definitions&visible
using&namespace&&&&&&&&&&&&&&&&&&&&&&&
int&main()&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//&function&header
{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//&start&of&function&body
&&&&cout&&&&&Come&up&and&C++&me&some&time.&;&&//&message
&&&&cout&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//&start&a&new&line
&&&&cout&&&&&You&won't&regret&it!&&&&&&&&//&more&output
&&&&return&10;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//&terminate&main()&返回值为0代表成功,非0返回值的含义由系统自定义
}&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//&end&of&function&body
打开命令窗口进行编译
[root@localhost&Desktop]#&g++&-o&test1&test.cpp
-o test1 test.cpp 从test.cpp编译生成test1文件,test1为可执行文件,没有后缀
如果不写-o test1 会默认生成一个a.out可执行文件
执行可执行文件
[root@localhost&Desktop]#&./a.out
Come&up&and&C++&me&some&time.
You&won't&regret&it!
获取main函数返回值
[root@localhost&Desktop]#&echo&$?
c/c++运行流程分解
预处理阶段
对c源文件预处理生成中间文件e.i
[root@localhost&c]#&g++&-E&funcuse.c&-o&e.i
对预处理文件进行处理生成汇编语言文件e.s
[root@localhost&c]#&g++&-S&e.i&-o&e.s
上述两部可以直接合并为
[root@localhost&c]#&g++&-s&e.i&-o&e.s
生成目标文件,目标文件是机器代码,但不能执行,必须将目标文件与其他目标文件或库文件连接生成可执行的二进制文件才能执行
[root@localhost&c]#&g++&-c&e.s&-o&e.o
生成执行文件
[root@localhost&c]#&g++&e.o&-o&result
运行result
[root@localhost&c]#&./result
在linux操作系统中运行程序必须指定程序所在的目录,除非程序的目录已经列在PATH环境变量中,所以程序前必须加./
注:echo $? &显示main函数的返回值(int型)
如果想让编译和运行同时进行可以采用如下命令:
gcc funcuse.c -o result && ./result
&&表示如果成功就。。。如果编译成功,会直接运行程序
可以将上述所有步骤合并写为
g++&funcuse.c&-o&result
g++&&-o&result&funcuse.c
直接生成可执行文件
头文件与源文件 & &
程序如果复杂的话,程序的各个部分会分别存储在不同的文件中,按照逻辑进行划分。
头文件的作用就是被其他的.cpp包含,本身并不参与编译,但实际上它们的内容却在多个.cpp文件中得到了 编译.
头文件中应该只放变量和函数的声明,而不能放它们的定义
这个规则是有三个例外的
头文件中可以写const对象的定义
头文件中可 以写内联函数(inline)的定义
头文件中可以写类 (class)的定义
分离式编译
如果将程序分成若干子程序,怎样在linux下进行编译呢?
下面以求圆的面积为例来说明
#ifndef&CIRCLE_H
#define&CIRCLE_H
class&Circle
&&&&private:
&&&&&&&&double&r;
&&&&public:
&&&&&&&&Circle();
&&&&&&&&Circle(double&R);
&&&&&&&&double&Area();
Circle.cpp
#include&&Circle.h&
#include&&iostream&
using&namespace&
Circle::Circle()
&&&&this-&r=5.0;
Circle::Circle(double&R)
&&&&this-&r=R;
double&Circle::&Area()
&&&&return&3.14*r*r;
#include&&Circle.h&
#include&&iostream&
using&namespace&
int&main(int&argc,&char&*argv[])
&&&Circle&c(3);
&&&cout&&&Area=&&&c.Area()&&
&&&return&0;
[root@localhost&cpp]#&g++&-c&Circle.cpp&-o&Circle.o
[root@localhost&cpp]#&g++&-c&main.cpp&-o&main.o
[root@localhost&cpp]#&g++&main.o&Circle.o&-o&main
[root@localhost&cpp]#&./main
Area=28.26
-c命令表示编译,头文件不许显式编译,但实际已经编译。如果只修改了一个源文件,只需要编译改动的文件
但如果我们的程序有几百个源程序的时候,怎么办?难道也要编译器重新一个一个的编译?
makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率
#此行为注释
main:&main.o&Circle.o
g++&main.o&Circle.o&-o&main
Circle.o:Circle.cpp
g++&-c&Circle.cpp&-o&Circle.o
main.o:main.cpp
g++&-c&main.cpp&-o&main.o
注意:g++命令开头的行前面必须有tab空格,不然会报错:&*** missing separator. &Stop
如果将名字命名为Makefile或makefile,只需要在命令行下敲入make就可以进行自动化编译
[root@localhost&cpp]#&make
g++&-c&main.cpp&-o&main.o
g++&-c&Circle.cpp&-o&Circle.o
g++&main.o&Circle.o&-o&main
[root@localhost&cpp]#&./main
Area=28.26
[root@localhost&c]#&gdb
GNU&gdb&(GDB)&Red&Hat&Enterprise&Linux&(7.2-48.el6)
Copyright&(C)&2010&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&&i686-redhat-linux-gnu&.
For&bug&reporting&instructions,&please&see:
&http://www.gnu.org/software/gdb/bugs/&.
调试前要先进性编译连接
[root@localhost&c]#&g++&-g&funcuse.c&-o&dbug
[root@localhost&c]#&gdb&dbug
Reading&symbols&from&/home/weiwei/Desktop/c/dbug...done.
(gdb)&list
1 #include&stdio.h&
3 int&main(){
for(int&i=0&;&i&5;&i++){
printf(&this&is&%d\n&,i);
(gdb)&list&3,5
3 int&main(){
for(int&i=0&;&i&5;&i++){
printf(&this&is&%d\n&,i);
Starting&program:&/home/weiwei/Desktop/c/dbug&
(gdb)&break&5
Breakpoint&1&at&0x8048487:&file&funcuse.c,&line&5.
Starting&program:&/home/weiwei/Desktop/c/dbug&
Breakpoint&1,&main&()&at&funcuse.c:5
printf(&this&is&%d\n&,i);
Missing&separate&debuginfos,&use:&debuginfo-install&glibc-2.12-1.25.el6.i686&libgcc-4.4.5-6.el6.i686&libstdc++-4.4.5-6.el6.i686
到断点处程序停止,继续运行输入continue
Continuing.
Breakpoint&1,&main&()&at&funcuse.c:5
printf(&this&is&%d\n&,i);
Continuing.
Breakpoint&1,&main&()&at&funcuse.c:5
printf(&this&is&%d\n&,i);
Continuing.
监测某一个值
(gdb)&watch&i
Hardware&watchpoint&2:&i
Continuing.
Hardware&watchpoint&2:&i
Old&value&=&3
New&value&=&4
0x&in&main&()&at&funcuse.c:4
for(int&i=0&;&i&5;&i++){
查看某一个特定的变量
(gdb)&print&i
自动显示变量的值,每次运行到断点处都会自动显示
(gdb)&display&i
查看当前自动显示的所有变量
(gdb)&info&display
Auto-display&expressions&now&in&effect:
Num&Enb&Expression
查看变量类型
(gdb)&whatis&i
type&=&int
单步执行,step进入函数内部( 使用return命令跳出来 ,跳出前可以使用finish执行完函数体),next把函数当成一条语句不进入函数内部
(gdb)&step&&
(gdb)&next
删除编号为1的断点
(gdb)&delete&1
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:119620次
积分:2455
积分:2455
排名:第12249名
原创:127篇
转载:23篇
评论:11条
(4)(7)(14)(19)(6)(10)(5)(6)(15)(3)(5)(2)(2)(3)(6)(5)(2)(1)(4)(9)(1)(2)(3)(2)(7)(8)温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
LOFTER精选
网易考拉推荐
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
在LOFTER的更多文章
loftPermalink:'',
id:'fks_',
blogTitle:'C++笔记',
blogAbstract:'C++程序的运行方式 基本数据类型 复合数据类型 循环和关系表达式 分支语句和逻辑操作符 函数重载和函数模板内存模型和名称空间 类的设计和使用 多态 虚函数 动态内存分配 继承 代码重用 友元 异常处理技术 string累和标准模板库 输入/输出等内容C++容纳了好几种编程模式 包括面向对象编程 通用编程和传统的过程化编程STL标准模板库数组 存储多个同类型的值 结构 存储多个不同类型的值 指针 标识内存位置类是用户定义的类型 对象是类的实例派生对象是基对象的特列 物理学家是科学家的特列auto-ptr类帮助管理动态分配的内存& STL提供了几种类容器(包括数组 队列 链表 集合和映射)的模板表示还提供了高效的通用算法库 这些算法可用于STL容器 也可用于常规数组 &',
blogTag:'',
blogUrl:'blog/static/',
isPublished:1,
istop:false,
modifyTime:0,
publishTime:3,
permalink:'blog/static/',
commentCount:0,
mainCommentCount:0,
recommendCount:0,
bsrk:-100,
publisherId:0,
recomBlogHome:false,
currentRecomBlog:false,
attachmentsFileIds:[],
groupInfo:{},
friendstatus:'none',
followstatus:'unFollow',
pubSucc:'',
visitorProvince:'',
visitorCity:'',
visitorNewUser:false,
postAddInfo:{},
mset:'000',
remindgoodnightblog:false,
isBlackVisitor:false,
isShowYodaoAd:false,
hostIntro:'',
hmcon:'1',
selfRecomBlogCount:'0',
lofter_single:''
{list a as x}
{if x.moveFrom=='wap'}
{elseif x.moveFrom=='iphone'}
{elseif x.moveFrom=='android'}
{elseif x.moveFrom=='mobile'}
${a.selfIntro|escape}{if great260}${suplement}{/if}
{list a as x}
推荐过这篇日志的人:
{list a as x}
{if !!b&&b.length>0}
他们还推荐了:
{list b as y}
转载记录:
{list d as x}
{list a as x}
{list a as x}
{list a as x}
{list a as x}
{if x_index>4}{break}{/if}
${fn2(x.publishTime,'yyyy-MM-dd HH:mm:ss')}
{list a as x}
{if !!(blogDetail.preBlogPermalink)}
{if !!(blogDetail.nextBlogPermalink)}
{list a as x}
{if defined('newslist')&&newslist.length>0}
{list newslist as x}
{if x_index>7}{break}{/if}
{list a as x}
{var first_option =}
{list x.voteDetailList as voteToOption}
{if voteToOption==1}
{if first_option==false},{/if}&&“${b[voteToOption_index]}”&&
{if (x.role!="-1") },“我是${c[x.role]}”&&{/if}
&&&&&&&&${fn1(x.voteTime)}
{if x.userName==''}{/if}
网易公司版权所有&&
{list x.l as y}
{if defined('wl')}
{list wl as x}{/list}Linux环境下的编译,链接与库的使用
bugouyonggan
发布时间: 11:43:33
为什么使用ullib有时会出现 undefined reference error 的错误?
为什么在动态链接库里ul_log会把日志输出到屏幕上?
为什么用-static 编译有时候会报warning?
我们在使用基础库或者第三方库的时候,经常遇到这样那样的问题,本文结合公司目前的主要环境,说明库的原理,使用的注意事项。
从程序到可执行文件
从hello world 说起
includeint main() { printf(“hello worldn”); return 0; }
上面这一段程序任何一个学过C语言的同学都是闭着眼睛都可以写出来,但是对于将这样一个源代码编译成为一个可执行文件的过程却不一定有所了解。 上面的程序如果要编译,很简单
gcc hello.c 然后./a.out就可以运行,但是在这个简单的命令后面隐藏了许多复杂的过程
一般来说,可以把这样的过程分成4个, 预编译, 编译, 汇编和链接
预编译 这个过程包括了下面的步骤
宏定义展开,所有的#define 在这个阶段都会被展开 预编译命令的处理,包括#if #ifdef 一类的命令 展开#include 的文件,像上面hello world 中的stdio.h , 把stdio.h中的所有代码合并到hello.c中 去掉注释 gcc的预编译 采用的是预编译器cpp, 我们可以通过-E参数来看预编译的结果,如: gcc -E hello.c -o hello.i 生 成的 hello.i 就是经过了预编译的结果 在预编译的过程中不会太多的检查与预编译无关的语法(#ifdef 之类的还是需要检查,
#include文件路径需要检查), 但是对于一些诸如 ; 漏掉的语法错误,在这个阶段都是看不出来的。 写过makefile的人都知道, 我们需要加上-Ipath 一系列的参数来标示gcc对头文件的查找路径
1.在一些程序中由于宏的原因导致编译错误,可以通过-E把宏展开再检查错误 , 这个在编写 PHP扩展, python扩展这些大量需要使用宏的地方对于查错误很有帮助。
如果在头文件中,#include 的时候带上路径在这个阶段有时候是可以省不少事情, 比如 #include , 这样在gcc的-I参数只需要指定一个路径,不会由于不小心导致,文件名正好相同出现冲突的麻烦事情. 不过公司由于早期出现了lib2和lib2-64两个目录, 以及头文件输出在include 目录下, 静态发布等一些历史原因, 有些时候使用带完整路径名的方式不是那么合适( 比如 #include 中间有一个include 显的很别扭).
不过个人认为所有的#include 都应该是尽量采用从cvs 根路径下开始写完整路径名的方式进行预编译的过程,只是受限于公司原有习惯和历史问题而显的不合适, 当然带路径的方式要多写一些代码,也是麻烦的事情, 路径由外部指定相对也会灵活一些.
编译 这个过程才是进行语法分析和词法分析的地方, 他们将我们的C/C++代码翻译成为 汇编代码, 这也是一个编译器最复杂的地方
gcc -S hello.i -o hello.s 可 以看到gcc编译出来的汇编代码, 现代gcc编译器一般是把预编译和编译合在一起,使用cc1 的程序来完成这个过程,在我们的开发机上有些时候一些同学编译大文件的时候可以用top命令看一个cc1的进程一直在占用时间,这个时候就是程序在执行编 译过程. 后面提到的编译过程都是指 cc1的处理包括了预编译与编译.
汇编 现在C/C++代码已经成为汇编代码了,直接使用汇编代码的编译器把汇编变成机器码(注意还不是可执行的) .
gcc -c hello.c -o hello.o 这里的hello.o就是最后的机器码, 如果作为一个静态库到这里可以所已经完成了,不需要后面的过程.
对于静态库, 比如ullib, COM提供的是libullib.a, 这里的.a文件其实是多个.o 通过ar命令打包起来的, 仅仅是为了方便使用,抛开.a 直接使用.o 也是一样的
gcc 采用as 进行汇编的处理过程,as 由于接收的是gcc生成的标准汇编, 在语法检查上存在不少缺陷,如果是我们自己写的汇编代码给as去处理,经常会出现很多莫名奇妙的错误.
链接 链接的过程,本质上来说是一个把所有的机器码文件组合成一个可执行的文件 上面汇编的结果得到一个.o文件, 但是这个.o要生成二执行文件只靠它自己是不行的, 它还需要一堆辅助的机器码,帮它处理与系统底层打交道的事情.
gcc -o hello hello.o 这样就把一个.o文件链接成为了一个二进制可执行文件. 我们提供的各种库头文件在编译期使用,到了链接期就需要用-l, -L的方式来指定我们到底需要哪些库。 对于glibc中的strlen之类常用的东西编译器会帮助你去加上可以不需要手动指定。
这个地方也是本文讨论的重点, 在后面会有更详细的说明
有些程序在编译的时候会出现 “linker input file unused because linking not done” 的提示(虽然gcc不认为是错误,这个提示还是会出现的), 这里就是把 编译和链接 使用的参数搞混了,比如
g++ -c test.cpp -I../../ullib/include -L../../ullib/lib/ -lullib 这样的写法就会导致上面的提示, 因为在编译的过程中是不需要链接的, 它们两个过程其实是独立的
链接的过程 这里先介绍一下,链接器所做的工作
其实链接做的工作分两块: 符号解析和重定位
符号包括了我们的程序中的被定义和引用的函数和变量信息
在命令行上使用 nm ./test
test 是用户的二进制程序,包括
可以把在二进制目标文件中符号表输出
09b8 A&bss_start04cc t call_gmon_start09b8 b completed.00788 dCTOR_END0780 d&CTOR_LIST09a0
D&data_start09a0 W data_start0630 tdo_global_ctors_aux04f0 t&do_global_dtors_aux09a8 D&dso_handle0798 d&DTOR_END0790
d&DTOR_LIST07a8 D&DYNAMIC09b8 A&edata09c0 A&end0668 T&fini0780
A&fini_array_end0780 A&fini_array_start0530 t frame_dummy0778 r&FRAME_END0970 DGLOBAL_OFFSET_TABLE&w&gmon_start&U&gxx_personality_v0@@CXXABI_1.00448
T _init0780 A __init_array_end… 当然上面由nm输出的符号表可以通过编译命令去除,让人不能直接看到。
链接器解析符号引用的方式是将每一个引用的符号与其它的目标文件(.o)的符号表中一个符号的定义联系起来, 对于那些和引用定义在相同模块的本地符号(注:static修饰的),编译器在编译期就可以发现问题,但是对于那些全局的符号引用就比较麻烦了.
下面来看一个最简单程序:
includeint foo();int main() { foo(); return 0; }
我们把文件命名为test.cpp, 采用下面的方式进行编译 g++ -c test.cppg++ -o test test.o 第一步正常结束,并且生成了test.o文件,到第二步的时候报了如下的错误
test.o(.text+0x5): In function&main':: undefined
reference tofoo()’collect2: ld returned 1 exit status 由于foo 是全局符号, 在编译的时候不会报错,等到链接的时候,发现没有找到对应的符号,就会报出上面的错误。但是如果我们把上面的写法改成下面这样
include//注意这里的static static int foo();int main() { foo(); return 0; }
在运行 g++ -c test.cpp, 马上就报出下面的错误:
test.cpp:19: error: ‘int foo()’ used but never defined 在编译器就发现foo 无法生成目标文件的符号表,可以马上报错,对于一些本地使用的函数使用static一方面可以避免符号污染,另一方面也可以让编译器尽快的发现错误.
在我们的基础库中提供的都是一系列的.a文件,这些.a文件其实是一批的目标文件(.o)的打包结果.这样的目的是可以方便的使用已有代码生成的结果,一般情况下是一个.c/.cpp文件生成一个.o文件,在编译的时候如果带上一堆的.o文件显的很不方便,像:
g++ -o main main.cpp a.o b.o c.o 这样大量的使用.o也很容易出错,在下使用 archive来讲这些.o存档和打包.
所以我们就可以把编译参数写成
g++ -o main main.cpp ./libullib.a 我们可以使用 ./libullib.a 直接使用 libullib.a这个库,不过gcc提供了另外的方式来使用:
g++ -o main main.cpp -L./ -lullib -L指定需要查找的库文件的路径, -l 选择需要使用的库名字,不过库的名字需要用 lib+name的方式命名,才会被gcc认出来. 不过上面的这种方式存在一个问题就是不区分动态库和静态库, 这个问题在后面介绍动态库的时候还会提到.
当存在多个.a ,并且在库之间也存在依赖关系,这个时候情况就比较复杂.
如果我们要使用lib2-64/dict, dict又依赖ullib, 这个时候需要写成类似下面的形式
g++ -o main main.cpp -L../lib2-64/dict/lib -L../lib2-64/ullib/lib -ldict -lullib -lullib 需要写在-ldict的后面, 这是由于在默认情况对于符号表的解析和查找工作是由后往前(内部实现是一个类似堆栈的尾递归). 所以当所使用的库本身存在依赖关系的时候,越是基础的库就越是需要放到后面.否则如果上面把 -ldict -lulib的位置换一下,可能就会出现 undefined reference to xxx 的错误. 一般来说对于基础库的依赖关系可以在平台上获取,
若存在一些第三方的依赖,就只有参考相关的帮助说明了
当然gcc提供了另外的方式的来解决这个问题
g++ -o main main.cpp -L../lib2-64/dict/lib -L../lib2-64/ullib/lib -Xlinker “-(” -ldict -lullib -Xlinker “-)” 可以看到我们需要的库被 -Xlinker “-(“ 和 -Xlinker “-)”  包含起来,gcc在这里处理的时候会循环自动查找依赖关系,不过这样的代价就是延长gcc的编译时间,如果使用的库非常的多时候,对编译的耗时影响还是非常大.
-Xlinker有时候也简写成”-Wl, “,它的意思是 它后面的参数是给链接器使用的.-Xlinker 和 -Wl 的区别是一个后面跟的参数是用空格,另一个是用”,”
我们通过nm 命令查看目标文件,可以看到类似下面的结果
~/lib2-64/dict/lib/x.html 1 9740 T&Z11ds_syn_loadPcS&2 9c62 T&Z11ds_syn_seekP16Sdict_search_synPcS1_i 3 7928 T&Z11dsur_searchPcS_S&4
& U&Z11ul_readfilePcS_Pvi 5 & U&Z11ul_writelogiPKcz 6 00a2 TZ12creat_sign32Pc
其中用 U 标示的符号_Z11ul_readfilePcS_Pvi (其实是ullib中的 ul_readfile) ,表示在dict的目标文件中没有找到ul_readfile函数.
在链接的时候,链接器就会去其他的目标文件中查找_Z11ul_readfilePcS_Pvi的符号
编 译的时候采用 -Lxxx -lyyy 的形式使用库,-L和-l这个参数并没有配对的关系,我们的一些Makefile 为了维护方便把他们写成配对的形式,给一些同学造成了误解. 其实我们完全可以写成 -Lpath1, -Lpath2, -Lpath3, -llib1 这样的形式.
在具体链接的时候,gcc是以.o文件为单位, 编译的时候如果写 g++ -o main main.cpp libx.o 那么无论main.cpp中是否使用到libx.o,libx.o中的所有符号都会被载入到mian函数中.但是如果是针对.a,写成g++ -o main main.cpp -L./ -lx, 这个时候gcc在链接的时候只会链接有被用到.o, 如果出现libx.a中的某个.o文件中没有任何一个符号被main用到,那么这个.o就不会被链接到main中
gcc编译.c文件的时候 和g++ 有一个不一样的地方, 就是在 g++ 中对于一个函数必须要先定义在再使用,比如上面的例子中需要先定义foo()才能被使用,但对于gcc编译的.c(如果是.cpp会自动换成C++编译) 文件, 可以不需要先定义, 而直接使用. 但这样会出现问题, 如果没有其他地方使用和这个函数同名的函数那么链接的时候会找不到这个函数. 但是如果碰巧在另外的地方存在一个同名函数,那么链接的时候就会被直接连接到这个函数上, 万一使用的时候偏偏传入参数或返回值的类型不对,那么这个时候就可能出现莫名奇妙的错误.
不过我们还是可以用-Wmissing-declarations参数打开这个检查
经过上面的符号解析后,所有的符号都可以找到它所对应的实际位置(U表示的链接找到具体的符号位置).
as 汇编生成一个目标模块的时候,它不知道数据和代码在最后具体的位置,同时也不知道任何外部定义的符号的具体位置,所以as在生成目标代码的时候,对于位置未知的符号,它会生成一个重定位表目,告诉链接器在将目标文件合并成可执行文件时候如何修改地址成最终的位置
g++和gcc 采用gcc 和g++ 在编译的时候产生的符号有所不同.
在C++中由于要支持函数重载,命名空间等特性,g++会把函数+参数(可能还有命名空间),把函数命变成一个特殊并且唯一的符号名.例如:
int foo(int a); 在gcc编译后,在符号表中的名字就是函数名foo, 但是在g++编译后名字可能就变成了_Z3fooi, 我们可以使用 c++filt命令把一个符号还原成它原本的样子,比如
c++filt _Z3fooi 运行的结果可以得到 foo(int)
由于在C++和纯C环境中,符号表存在不兼容问题,C程序不能直接调用C++编译出来的库,C++程序也不能直接调用C编译出来的库.为了解决这个问题C++中引入了 extern “C” 的方式.
extern “C” int foo(int a); 这样在用g++编译的时候, c++的编译器会自动把上面的 int foo(int a)当做C的接口进行符号转化.这样在纯C里面就可以认出这些符号.
不过这里存在一个问题,extern “C” 是C++支持的,gcc并不认识,所有在实际中一般采用下面的方式使用++
ifdef&cplusplus extern “C” { #endifint foo(int a); #ifdef&cplusplus } #endif
这样这个头文件中的接口即可以给gcc使用也可以给g++使用, 当然在extern “C” { } 中的接口是不支持重载,默认参数等特性
在 我们的64位编译环境中如果有gcc的程序使用上面方式g++编译出来的库,需要加上-lstdc++, 这是因为,对于我们64位环境下g++编译出来的库,需要使用到一个 gxx_personality_v0 的符号,它所在的位置是/usr /lib64/libstdc++.so.6 (C++的标准库iostream都在里面,C++程序都需要的). 但是在我们的32位2.96 g++编译器中是不需要gxx_personality_v0,所有编译可以不加上
-lstdc++
在linux gcc 中, 只有在源代码使用 .c做后缀,并且使用gcc编译才会被编译成纯C的结果,其他情况像 g++ 编译.c文件,或者gcc 编译.cc, .cpp文件都会被当作C++程序编译成C++的目标文件, gcc和g++唯一的不同在于gcc不会主动链接-lstdc++ 在 extern “C” { }中如果存在默认参数的接口,在g++编译的时候不会出现问题,但是gcc使用的时候会报错.因为对于函数重载,接口的符号表还是和不用默认参数的时候是一样的. 编译器版本问题 目前公司内部使用的gcc版本主要分两种
32位 gcc 2.96 64位 gcc 3.4.4 (这是编译机的版本号,我们的开发机多数是gcc 3.4.5, 小版本号的差异,目前看来不会对程序会带来影响) 有时候在32位环境中经常会出现"undefined reference error”的错误,这个问题多数是由于 gcc 的版本问题造成的,我们许多的32位机器上的编译器都是3.x的版本,gcc 从2到3做了很大的改动,c++的符号表的表现有所区别,导致gcc3的编译器不能链接由gcc2.96编译出来的库.我们的基础库在lib2下的都是采 用静态发布(直接发布最后的二进制库,而不是在需要的时候重新编译).不过在gcc3的glibc中考虑了向下兼容性使的可以正常运行由gcc
2.96上编译出来的二进制程序. 我们现在有一种方式是在gcc2.96环境下编译出来的二进制程序放到64位机器上去运行,如果我们是一个新的 64位机器环境上运行程序,实际上这是无法运行的,我们的程序之所以可以这样做,是由于在我们的64位机器上装上32位程序运行的环境,包括载入32位程 序的载入器,对应的各种动态库,可以在64位机器上/usr/lib/rh80目录下看所使用各种动态库,不过这些库的版本与我们的开发机编译机上版本有 所不同,有些时候我们会发现如果64位机器上的32位程序运行出core, 把core文件放到开发机上进行调试会看到出现在glibc的动态库的函数都core在一些很奇怪的位置,根本不是我们程序中调用的位置,这里很重要的原
因就在于动态库的版本不一样
符号表冲突 我们在编译程序的时候时常会遇到类似于
multiple definition of `foo()’ 的错误.
这些错误的产生都是由于所使用的.o文件中存在了相同的符号造成的.
int foo() { return 30; } liby.cpp
int foo() { return 20; } 将libx.cpp, liby.cpp编译成 libx.o和liby.o两个文件
g++ -o main main.cpp libx.o liby.o 这个时候就会报出 multiple definition of `foo()’ 的错误
但是如果把libx.o和liby.o分别打包成libx.a和liby.a用下面的方式编译
g++ -o main main.cpp -L./ -lx -ly 这 个时候编译不会报错,它会选择第一个出现的库,上面的例子中会选择libx中的foo. 但是注意不是所有的情况都是这样的,由于链接是以.o为单位的,完全可以不用某个.o的时候才不会出错误,否则依然会出现multipe的错误, 这种情况下的建议是查看一下这些函数的行为是什么样子,是否是一致的,如果不一致,还是想办法规避, 如果是一致的话可以用 -Wl,–allow-multiple-definition 强制编译过去,这样会使用第一个碰到的库,但不推荐这样做.
可以通过 g++ -o main main.cpp -L./ -lx -ly -Wl,–trace-symbol=_Z3foov的命令查看符号具体是链接到哪个库中,
g++ -o main main.cpp -L./ -lx -ly -Wl, –cref 可以把所有的符号链接都输出(无论是否最后被使用) 小提示:
对于一些定义在头文件中的全局常量,gcc和g++有不同的行为,g++中const也同时是static的,但gcc不是
例如: foo.h 中存在一个
constint INTVALUE = 2000; 的全局常量
有两个库 a和b, 他们在生成的时候有使用到了  INTVALUE,如果有一个程序main同时使用到了 a库和b库,在链接的时候gcc编译的结果就会报错,但如果a和b都是g++编译的话结果却一切正常.
这个原因主要是在g++中会把INTVALUE 这种const常量当做static的,这样就是一个局部变量,不会导致冲突,但是如果是gcc编译的话,这个地方INTVALUE会被认为是一个对外的全局常量是非static的,这个时候就会造成链接错误
小提示 上说了对于a库和b库出现同样符号的情况会有冲突, 但是在实际中有这么一种情况, a库定义的foo的接口,在有b库的情况下是一种行为,在没有b库的情况下又想要一种行为。为解决这个问题引入了弱连接的机制, 前面我们看到nm后,有些符号前面有T标志,这个表示的是这个符号是一个强连接。 如果看有W的表示,那么就表示这个符号是弱连接。如果有一个同名的库也有相同的符号并且是强连接,那么就可以替代掉他(如果也是弱连接,会存在先后顺序用 谁的问题)。 glibc中的符号都是弱连接, 我们可以在我们的程序中编写 open,
write之类的函数去替换掉glibc中的实现。
如果我们要自己写弱连接的函数可以采用gcc扩展
attribute((weak)) const int func();
来表示一个符号是弱连接
~/lib2-64/dict/lib/x.html 1 9740 T&Z11ds_syn_loadPcS&2 9c62 T&Z11ds_syn_seekP16Sdict_search_synPcS1_i 3 7928 T&Z11dsur_searchPcS_S&4
& U&Z11ul_readfilePcS_Pvi 5 & U&Z11ul_writelogiPKcz 6 00a2 TZ12creat_sign32Pc
对于静态库的使用,有下面两个问题
当我们需要对某一个库进行更新的时候,我们必须把一个可执行文件再完整的进行一些重新编译 在程序运行的时候代码是会被载入机器的内存中,如果采用静态库就会出现一个库需要被copy到多个内存程序中,这个一方面占用了一定的内存,另一方面对于CPU的cache不够友好 链接的控制,从前面的介绍中可以看到静态库的连接行为我们不好控制,做不到在运行期替换使用的库 编译后的程序就是二进制代码,有些代码它们涉及到不同的机器和环境,假设在A 机器上编译了一个程序X, 把它直接放到B机器上去运行,由于A和B环境存在差异,直接运行X程序可能存在问题,这个时候如果把和机器相关的这部分做成动态库C,并且保证接口一致,
编译X程序的时候只调用C的对外接口.对于一般的用户态的X程序而言,就可以简单的从A环境放到B环境中.但如果是静态编译,就可能做不到这点,需要在B 机器上重新编译一次. 动态链接库在linux被称为共享库(shared library,下文提到的共享库和动态链接库都是指代shared library),它主要是为了解决上面列出静态库的缺点而提出的.目前在公司内部许多产品线也开始逐步采用这种方式。 共享库的使用 共享库的使用主要有两种方式,一种方式和.a的静态库类似由编译器来控制,其实质和二进制程序一样都是由系统中的载入器(ld-linux.so)载入,另一种是写在代码中,由我们自己的代码来控制.
还是以前面的例子为例:
g++ -shared -fPIC -o libx.so libx.cpp 编译的时候和静态库类似,只是加上了 -shared 和 -fPIC, 将输出命名改为.so
然后和可执行文件链接.a一样,都是
g++ -o main main.cpp -L./ -lx 这 样main就是调用 libx.so, 在运行的时候可能会出现找不到libx.so的错误, 这个原因是由于动态的库查找路径的问题, 动态库默认的查找路径是由/etc /ld.so.conf文件来指定,在运行可执行文件的时候,按照顺会去这些目录下查找需要的共享库。我们可以通过 环境变量 LD_LIBRARY_PATH来指定共享库的查找路径(注:LD_LIBRARY_PATH的优先级比ld.so.conf要高).
命令上运行 ldd ./main 我们可以看到这个二进制程序在运行的时候需要使用的动态库,例如:
libx.so =& /home/bnh/tmp/test/libx.so (0x003cb000)
libstdc++.so.6 =& /usr/lib/libstdc++.so.6 (0x)
libm.so.6 =& /lib/tls/libm.so.6 (0x00bde000)
libgcc_s.so.1 =& /lib/libgcc_s.so.1 (0x00c3e000)
libc.so.6 =& /lib/tls/libc.so.6 (0x00aab000)
这里列出了mian所需要的动态库, 如果有看类似 libx.so=&no found的错误,就意味着路径不对,需要设置LD_LIBRARY_PATH来指定路径
小提示: 有一个特殊的环境变量LD_PRELOAD, 可以强行替换共享库中运行的符号。 export LD_PRELOAD= “xxx.so”, 如果你程序运行过程中遇到了和xxx.so同名的符号,这个时候程序会使用到xxx.so中的符号
手动载入共享库 除了采用类型于静态库的方式来使用动态库,我们还可以通过由代码来控制动态库的使用。
这种方式允许应用程序在运行时加载和链接共享库,主要有下面的四个接口
载入动态链接库
void&dlopen(constchar&filename, int flag); 获取动态库中的符号 void&dlsym(void&handle, char&symbol); 关闭动态链接库 void dlclose(void&handle);
输出错误信息 constchar *dlerror(void); 看下面的例子:
typedefint foo_t();foo_t * foo = (foo_t*) dlsym(handle, “foo”); 通过上面的方式我们可以载入符号”foo”所对应的地址,然后通过强制类型转换给一个函数指针,当然这里函数指针的类型需要和符号的原型类型保持一致,这些一般是由共享库所对应的头文件提供.
这 里要注意一个问题,在dlsym中载入的符号表示是和我们使用nm 库文件所看到符号表要保持一致,这里就有一个前面提到的 gcc和g++符号表的不同,一个 int foo(), 如果是g++编译,并且没有extern “C”导出接口,那么用dlsym载入的时候需要用 dlsym(handle, “_Z3foov”) 方式才可以载入函数 int foo(), 所以建议所以的共享库对外接口都采用 extern “C”的方式导出 纯C接口对外使用,这样在使用上也会比较方便
dlopen 的flag 标志可以选择 RTLD_GLOBAL , RTLD_NOW, RTLD_LAZY. RTLD_NOW, RTLD_LAZY只是表示载入的符号是一开始就被载入还等到使用的时候被载入,对于多数应用而言没有什么特别的影响.这两个标志都可以通过| 和RTLD_GLOBAL一起连用
这里主要是说明RTLD_GLOBAL的功能,考虑这样的一个情况:
我们有一个 main.cpp ,调用了两个动态 libA, 和 libB, 假设A中有一个对外接口叫做 testA, 在main.cpp可以通过dlsym获取到testA的指针,进行使用.但是对于libB 中的接口,它是看到不libA的接口,使用testA 是不能调用到libA中的testA的,但是如果在dlopen 打开libA.so的时候,设置了RTLD_GLOBAL这个选项,就可以把libA.so中的接口升级为全局可见, 这样在libB中就可以直接调用libA中的testA,如果在多个共享库都有相同的符号,并且有RTLD_GLOBAL选项,那么会优先选择第一个。
另 外这里注意到一个问题, RTLD_GLOBAL使的动态库之间的对外接口是可见的,但是动态库是不能调用主程序中的全局符号,为了解决这个问题, gcc引入了一个参数-rdynamic,在编译载入共享库的可执行程序的时候最后在链接的时候加上-rdynamic,会把可执行文件中所有的符号变成 全局可见,对于这个可执行程序而言,它载入的动态库在运行中可以直接调用主程序中的全局符号,而且如果共享库(自己或者另外的共享库 RTLD_GLOBAL) 加中有同名的符号,会选择可执行文件中使用的符号,这在一些情况下可能会带来一些莫名其妙的运行错误。
/usr/sbin/lsof -p pid 可以查看到由pid在运行期所载入的所有共享库 共享库无论是通过dlopen方式载入还是载入器载入,实质都是通过 mmap的方式把共享库映射到内存空间中去。mmap的参数MAP_DENYWRITE可以在修改已经被载入某个进程文件的时候阻止对于内存数据的修改, 由于现在内核中已经禁用这个参数,直接导致的结果就是如果对mmap的文件进行修改,这个时候的修改会被直接反映到已经被mmap映射的空间上。由于内核 的不支持,使得共享库不能在运行期进行热切换,共享库在更新的时候需要由载入的程序通过一些外部的方式来判断,主动使用dlclose,并且dlopen
重新载入共享库,如果是载入器载入那么需要重启程序。另外这里的热切换指的是直接copy覆盖原有的共享库,如果是采用mv或者软连接的方式那么还是安全 的,共享库被mv后不会影响原来的已经载入它的程序。 g++ 加上 -rdynamic 参数实质上相当于ld链接的时候加上-E或者–export-dynamic参数,效果与g++ -Wl,-E或者g++ -Wl,–export-dynamic的效果是一样的。
静态库和动态库的混合编译 目前我们多数的库都是以静态库的方式提供,但是现在有许多地方出于运维和升级的考虑使用了许多动态链接库,这样不可避免的出现了大量的静态库与动态库的混合使用,经常会出现一些奇怪的错误,使用的时候需要有所关注
对于一般情况下,只要静态库与共享库之间没有依赖关系,没有使用全局变量(包括static变量),不会出现太多的问题,下面以出现的问题作例子来说明使用的注意事项。
baidugz与zlib的冲突
具体的说明可以参看wiki LibBaidugz baidugz 是百度早期用来解压压缩网页,可以自动识别多数的网页压缩格式具有一定的容错性,但是由于baidugz是早期zlib版本直接修改而来,出现与系统中版本不一致的时候就可能导致问题。
在 /usr/lib64/ 下可以看到 libz.so, 我们在直接使用系统zlib的时候多是在链接的时候加上 -lz 就可以了。程序在运行的时候会直接到系统的目录下去寻找libz.so,并且在运行期被载入。
早 期的zlib代码中有一部分函数和变量,虽然没有通过zlib.h对外公开,但是还是采用了extern的方式被其他的.c文件使用(这里涉及到一个问题 就是一个源码中的变量或接口要被同一个库中其它地方使用,只能被extern,但extern 后就意味着可以被其它任意使用这个库的程序看到和使用, 无论是否在对外接口中声明), 还有个别接口可以使用static但没有使用static。 这部分对内公开(实际上对外也公开了)的接口, 在baidugz的修改过程中没有被修改,在后来升级64位版本的时候,由于系统中的zlib与baidugz使用的zlib相差过大,zlib在本身的
升级过程中也没有过多的考虑这个问题(它假设不会有并存的情况), 导致在链接的过程出现错误.
在编写动态库的过程中,可以static的函数即使没有暴露在头文件也需要尽量static,避免和外界冲突。那种没有对外公开接口就无所谓加不加static的观点是存在一定风险的.
有 些程序使用 using namespace {} 这样的匿名命名空间来规避冲突的问题,从编译器角度而言,在代码中使用确实不会产生冲突。 不过采用dlopen的方式却还是可以通过强制获取符号的方式运行在共享库中使用using namespace {}包含起来的函数,但static的函数是不能被dlopen方式强制获取的。
地址无关代码
在64位下编译动态库的时候,经常会遇到下面的错误
/usr/bin/ld: /tmp/ccQ1dkqh.o: relocation R_X86_64_32 against `a local symbol’ can not be used when ma recompile with -fPIC 提示说需要-fPIC编译,然后在链接动态库的地方加上-fPIC的参数编译结果还是报错,需要把共享库所用到的所有静态库都采用-fPIC编译一边才可以成功的在64位环境下编译出动态库。
这里的-fPIC指的是地址无关代码
这 里首先先说明一下装载时重定位的问题,一个程序如果没有用到任何动态库,那么由于已经知道了所有的代码,那么装载器在把程序载入内存的过程中就可以直接安 装静态库在链接的时候定好的代码段位置直接加载进内存中的对应位置就可以了。但是在面对动态的库的时候 ,这种方式就不行了。假设需要载入共享库A,但是在编译链接的时候使用的共享库和最后运行的不一定是同一个库,在编译期就没办法知道具体的库长度,在链接 的时候就没办法确定它或者其他动态库的具体位置。另一个方面动态库中也会用到一些全局的符号,这些符号可能是来自其他的动态库,这在编译器是没办法假设的
(如果可以假设那就全是静态库了)
基于上面的原因,就要求在载入动态库的时候对于使用到的符号地址实现重定位。在实现上在编译链接的时候不做重定位操作,地址都采用相对地址,一但到了需要载入的时候,根据相对地址的偏移计算出最后的绝对地址载入内存中。
但是这种采用装载时重定位的方式存在一个问题就是相同的库代码(不包括数据部分)不能在多个进程间共享(每个代码都放到了它自己的进程空间中),这个失去了动态库节省内存的优势。
为了解决这个问题,ELF中的做法是在数据段中建立一个指向那些需要被使用(内部的位置无关简单采用相对地址访问就可以实现)的地址列表(也被称为全局偏移表,Global offset table, GOT). 可以通过GOT相对应的位置进行间接引用.
对 于我们的32位环境来说, 编译时是否加上-fPIC, 都不会对链接产生影响, 只是一份代码的在内存中有几个副本的问题(而且对于静态库而言结果都是一样的).但在64位的环境下装载时重定位的方式存在一个问题就是在我们的64位环 境下用来进行位置偏移定位的cpu指令只支持32位的偏移, 但实际中位置的偏移是完全可能超过64位的,所以在这种情况下编译器要求用户必须采用fPIC的方式进行编译的程序才可以在共享库中使用
从理论上来说-fPIC由于多一次内存取址的调用,在性能上会有所损失.不过从目前的一些测试中还无法明显的看出加上-fPIC后对库的性能有多大的损失,这个可能和我们现在使用的机器缓存以及大量寄存器的存在相关.
-fPIC与-fpic 上面的介绍可以看到,gcc要使用
来源:http://blog.csdn.net/bugouyonggan/article/details/

我要回帖

更多关于 汇编申请内存 的文章

 

随机推荐