pc201502061243pc版微信怎么看朋友圈理解

新疆&|&dfsdfs
电气安装、弱电系统图中,3*PC20FC如何理解?是否理解为3根PC20的管子延地面敷设?
注:请在以下交流中文明用语,共创和谐交流圈!
公告:此平台禁止发布任何违法和广告信息,违者承担相应法律责任。
侵权(诽谤、抄袭、冒用等)
亲爱的用户,哒哒客服改版了
入口迁移至G+工作台在线服务页面,请您前往G+连接哒哒妹子吧~温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
自省,犹豫,较真……
LOFTER精选
网易考拉推荐
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
稍后将更新推出常见递归图形的解法。
阅读(4089)|
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
loftPermalink:'',
id:'fks_',
blogTitle:'pc-logo常见递归的理解与设计',
blogAbstract:'
&&&&&& logo语言里有一种递归算法,就是在子程序内部调用自己的算法。这种算法一开始我毫无头绪,网上查找了多份资料,加上自己的实践,对常见的递归算法画出的图形的设计有了自己的理解,现总结如下。
&&&&&&& 一、递归算法程序的必用语句
1.递归程序定义
一般递归程序都有至少一个变量,用以控制递归的次数。
例如:TO&& DBX& :B
2.确定递归停止的条件
因为递归是自己调用自己,如果没有定义如何结束,那么程序将不断重复,不断循环,没有终点,所以第一个关键语句就是要确定什么时候,什么情况下停止递归。',
blogTag:'logo,小学,递归,理解,例子',
blogUrl:'blog/static/',
isPublished:1,
istop:false,
modifyTime:2,
publishTime:2,
permalink:'blog/static/',
commentCount:3,
mainCommentCount:1,
recommendCount:6,
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}下面我来讲解一些关于网络的基础知识,这些知识有利于帮助我们今后学习TCP/IP协议相关知识打好基础。
首先从网络的诞生背景讲起。一开始计算机的运作是独立存在的(独立模式),每个计算机有各自的业务,用户需要在不同的计算机上执行特定的业务逻辑。用户采用卡带的方式将程序和数据输入计算机让计算机进行运算。之后出现了分时系统,分时系统具有“多终端,一主机 ”的特点,由于分时系统CPU时间片轮转的特性让用户有了“一人一机”的错觉。70年代后计算机日渐小型化,慢慢的出现了多台主机连接通信的技术,紧接着到80年代,互连多种计算机(包括超级计算机、PC等)的网络诞生。90年代后提出“瘦身”和“多供应商连接”的口号,电子邮件和万维网技术取得长足的发展。如今的社会已然是互联网的时代,甚至于一切皆“TCP/IP”连接。计算机在规模上从WAN(广域网)到LAN(局域网)。
下面说一下协议相关的东西。协议简单来说就是通信事物之间进行沟通的规则。由于计算机不具备人类的认知能力,所以要想实现计算机之间的通信,制定通信规则然后通信双方共同遵守该规则是通信的前提。这里提一下分组交换协议,后面要用到。分组交换是指将大数据切割成一个个包(Packet)进行传输的方法。注意切成包之后一定要在包上加上数据的源主机地址和目标地址、分组序号这些信息,即报文首部。注意一定要贴上这些东西,不然你怎么把数据发送出去,怎么让数据在目标地址那边把大数据凑出来?穿插了一下分组交换,下面继续前面刚刚提到的协议。协议开始是由各个厂家各自制定的,这样的结果就是用户只能使用同一个厂家的设备进行通信……后来ISO看不下去了,就制定了OSI标准协议(顺便给出了OSI参考模型)。虽然ISO给出的OSI协议最后并不流行,但是它提出了这种思想。今天TCP/IP协议正在一统天下。(注:TCP/IP不是ISO制定的,它是IETF制定的)
既然说到了分组交换,就把现代网络通信方式分组交换说一下吧。在这之前先说一下更加悠久的电路交换技术。先看一下下面这个图:
在电路交换技术中,计算机先通过电路交换机连接,此外还有另外一批计算机也通过电路交换机连接。如果计算机A与计算机F想要建立连接,怎么办呢?A与F通过中间的电路连接在一起。这样的方式不用多想存在许多弊端:不能同时并发通信,因为中间的电路每次只能给两台计算机通信用!于是人们想出了更加优秀的分组交换技术,看上图,就是将左侧的计算机需要发送的数据全部送到路由器里面切割成一个个的包(Packet)然后缓存在路由内部,路由按照队列的方式将这些包发送出去。这样就解决了电路交换技术中的一些问题。但是它也有些毛病,比如当路由缓存满时可能溢出导致数据丢失或者无法发送的情况。
OSI参考模型
上面说到了ISO的OSI协议,后来它并没有被大量采用,不过ISO的OSI参考模型却成为今天网络体系结构的标准模型。让我们来看看这个是什么东东,为什么能影响力这么大?其实OSI参考模型就是将网络通信本来复杂无厘头的过程分成了7层(见下面),每一层都有它们各自管理的事情,相同层次之间可以使用协议通信,而不同层次之间又可以通过接口来通信。这个分层是如此的美妙,比如在这个系统内某一个层次发生更改或者拓展并不会影响到整个系统的特点,所以到今天它也一直在使用着。不过它也有一些劣势,像过分分层导致有些功能实现逻辑不得不重复实现。
下面我们一起来看看协议中7层各自的功能。博主以一个场景为例来介绍7层功能,欢迎吐槽。以小明向小红发送电子邮件为例。小明写好了一封邮件“早上好”,填好了小红的Email地址,然后点击“发送 ”按钮,应用层就开始摩拳擦掌了。应用层利用协议先在数据的前端附加一个首部(标签)信息,包括邮件内容“早上好”和收件人小红的信息。这些信息会在小红的应用层中被分析保存,此外还存在异常处理。小明的应用层把数据处理后传到下面的表示层去。为什么要有表示层捏?试想一下,小明的电脑他使用Outlook来发邮件,小红的电脑使用网页版来收文件,假设这两个软件的编码格式不同,如果直接进行收发会出现怎样的情况?当然是乱码咯。所以就要有表示层来解决这种情况,在发送方将数据转换成网络通用的标准数据格式,然后在接收方将这些特定格式的数据再次解码为本地的编码格式,这样就没有乱码问题了。注意表示层也会附加首部信息。好了,现在经过两层的处理,数据传到了会话层。这一层又是干嘛的呢?一封邮件看不出来它的作用,我就再举例吧。小明发完“早上好”又发了一封“吃了没”,会话层的作用就是解决建立连接,按照什么顺序发送的问题的,但是它不发送。比如说,建立一次连接发送“早上好”然后断开,再建立一次连接发送“吃了没”再断开;或者建立一次连接同时发送“早上好”“吃了没”。确定怎么建立连接,怎么发送是这一层的问题,这是连接管理层。接下来会话层会把数据继续传递至下一层,传输层以下就是真正发挥网络传输的地方了。上面的数据传到传输层,传输层就确定小明的主机与小红的主机是否连接了,没有连接就建立连接,通信完毕就断开连接。此外传输层会判断数据是否完整地到达了小红的主机,如果没有就重新发一遍。可以看出传输层具有保障数据可靠性的功能。经过了传输层,我们的连接就建立了。然而它并不会真的把数据发送出去,这就要下面的网络层和数据链路层来处理了。网络层是指从原地址到目标地址数据通信的过程,是整个通信的过程。而数据链路层是指传递过程中的中转站传递(见下面)。就好比小明的邮件要传到一个个别的设备最终才能到达小红的主机上。
最后一层是物理层,顾名思义,就是通过物理介质来实现数据传递的层级。这是通信的根本,没有物理介质通信不可能实现。在这一层里数据从0,1转换为电压或者脉冲光传递,通过MAC地址来实现传递。
经过这么漫长的步骤,小明终于把数据发送了出去。那么小红是怎么接收数据的呢?其实就是上面过程倒叙一遍啦。小红的主机从物理层开始不断将小明发送来的数据进行反向解码,从而一层层剥离最终得到想要的数据。关于OSI参考模型需要在今后的学习中不断更新理解,这里可能存在偏差。
上面列举了小明和小红的通信过程,我们称之为面向有连接型连接,因为双方都建立了连接后才进行的通信。此外还有面向无连接型通信,UDP大家都知道吧,发送方就是一个劲地发,不管别人是否接收。这种方式有什么好处呢?它可以让处理变得简单,你发就行,我接收就行,不用去执行建立通信那一套东西。其实上面的两种传输方式是一种分类,此外还有根据接收端数量来分类的方式,分为单播、广播、多播和任播*。这不难理解(见下图)
注意:多播和任播的客户端全体拥有同一个地址。
说了这么多终于说到地址了。上面的通信过程必须有地址才能实现。地址具有两个重要的特性:唯一性和层次性。唯一性不多说了,在同一个通信网络中地址必须唯一指定,不然怎么通信……说说层次性,层次性是指地址中能够实现快速定位的能力。比如从一个电话号码可以看出用户的国家、省市、区名。IP地址由于其拥有网络号和主机号而拥有层次性。两个相同网络号的IP地址的主机的组织结构、提供商类型和地狱分布集中。而MAC地址不具有层次性。下面说说寻址的问题。网络传输过程中的节点会根据分组数据的地址判断是从哪个网卡发出来的,MAC寻址会参考地址转发表,IP寻址会参考路由控制表,具体寻址参考下面这张图,一目了然。
网络的构成要素
上面讲了这么多,最后从宏观上,从现实中来感知一下网络的存在吧。网络的物理设备组成包括:网卡、中继器、网桥/2层交换机、路由/3层交换机、4-7层交换机、网关还有电缆。计算机通信是通过线缆来完成的,线缆的传输速率(又称带宽)指单位时间传输的数据量的多少。网络接口卡(NIC)有时也被称为网络适配器、网卡、LAN卡,是计算机连网的设备,具有独一无二的MAC地址。中继器处于物理层,是对电缆信号进行波形调整和放大然后继续传输的设备。可以认为集线器每个端口都是中继器。网桥(又称二层交换机)负责在数据链路层中的数据帧的重构、错误数据帧丢弃和传输数据帧。(维基百科上解释为& “橋接器将网络的多个网段在数据链路层(OSI模型第2层)连接起来(即桥接)。”)路由(又称三层交换机) 处于网络层,根据IP地址连接两个网络对分组报文进行转发,此外还有网络安全和分担网络负荷的功能。网关负责从传输层到应用层的数据进行转换和转发的设备。互联网邮件与手机邮件之间的转换服务。互联网与手机之间设置了一道网关,网关负责读取完各种不同的协议后,对它们逐一进行合理的转换,再将相应的数据转发出去。代理服务器也是网关的一种,称为应用网关。个人对于中继器、网桥、路由的理解就是数据传输从底层硬件逐渐往上层的管理设备。
阅读(...) 评论()pclint error10和error129怎么理解_百度知道
pclint error10和error129怎么理解
我有更好的答案
error 10:是碰上了PClint不认识的类型,可以加选项 +rw(类型),就可以屏蔽这类提示。error 129:在一个上下文中期望一个声明,但是发现一个标识符,甚至,标识符不能跟着一个'(' 或一个'[。意思是在上下文中缺少一个'Symbol' 标识符的声明,需要添上。error 322: 是不能打开头文件,可能你包含的头文件路径不对。
为您推荐:
其他类似问题
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。如何理解DLL不是地址无关的?DLL与ELF的对比分析
前言《程序员的自我修养》中提到“PE DLL的代码段并不是地址无关的”,恐怕好多人看完都是一知半解。本文将DLL与Linux下ELF格式的动态共享对象(.so文件)对比分析,详细解释为何DLL不是地址无关的;并简单分析这样做的出发点和利弊。这里提到的“地址无关(Position-Independent Code,PIC)”是指对于动态链接库(Windows下的.dll,Linxu下的.so)的代码段(.text段)的地址无关,即是否可以多个进程共用同一个动态链接库的代码段。Update:关于x64下的PIC本文介绍的是32位下的PIC实现,x64下的PIC有重大的改进,参考。关于相对寻址想要弄清楚PIC,应当首先理解相对寻址的一些特点,这也是很多人忽略的。我们知道,对于同一个模块来说,模块内部的函数之间的相对位置都是固定的,所以一般模块内的函数调用都是采用的相对地址调用。准确地来说,模块内部的跳转、函数调用都可以是相对地址调用,或是基于寄存器的相对调用。例如:8048344 &bar&:
mov %esp, %ebp
8048349 &foo&:
e8 e8 ff ff ff
call 8048344 &bar&
b8 00 00 00 00
mov $0x0, %eax
...8048344对应函数bar(),8048349对应函数foo();foo()中有对bar()的调用,即8048357反汇编得到的call 8048344 &bar&。实际上这就是一个相对地址调用,8048357的机器码为e8 e8 ff ff ff,其中e8即call的指令码,而其后的e8 ff ff ff为-24的补码(0xFFFFFFE8),则实际调用地址为0x804835c + (-24) = 0x8048344,即bar()的地址。但是数据的相对寻址往往没有相对于当前指令地址(PC)的寻址方式,也就是说虽然同一模块内变量的相对位置也是固定的,但我们不能像函数调用那样指定一个相对偏移就得到变量的值,因为没有这样的指令。这是至关重要的。ELF的PIC实现别着急,我们先看看ELF格式文件的PIC实现,有对比才能说明DLL的地址无关性。一般来说,要实现PIC,我们要考虑4种类型的地址引用方式:模块内部调用或转跳模块内部数据访问模块间数据访问模块间调用或跳转对于第1种我们在上一节已经讲到了,这种情况不需做任何处理。下面来关注ELF对后面3种情况的PIC处理。注意:“模块内部调用或转跳”实际没这么简单,还涉及到全局符号介入的问题。模块内部数据访问数据的相对寻址没有相对于当前指令地址(PC)的寻址方式,所以ELF用了一个很巧妙的方法来得到当前PC值,然后再加上一个偏移量就可以达到访问相应变量的目的了。例如:
void bar(){
}由于a是静态变量,所以a在.data段;而bar()则在.text段中,bar()对a进行了赋值。需要注意虽然两者不在同一个段内,但同属一个模块,相对位置是固定的。设想如果系统支持数据相对寻址,那么a = 1只需要像函数调用那样指定一个偏移量然后赋值就好了,但很可惜现代的系统都不支持。来看看ELF怎么做的:0000044c &bar&:
44f: e8 40 00 00 00
call 494 &__i686.get_pc_thunk.cx&
454: 81 c1 8c 11 00 00
add $0x118c, %ecx
45a: c7 81 28 00 00 00 01
movl $0x1, 0x28(%ecx)
&__i686.get_pc_thunk.cx&:
494: 8b 0c 24
mov (%esp), %ecx
ret44f是一个相对地址调用,454 + 40 = 494,调用__i686.get_pc_thunk.cx,这个函数直接将返回地址(下一条指令地址,就是PC啦)放到ecx寄存器(执行call指令函数调用时,下一指令地址会被压到栈顶,而esp寄存器始终指向栈顶,(%esp)即获取栈顶值,494即将此值交给ecx),然后454给PC加上一个偏移量0x118c,45a则继续添加偏移量0x28,然后将1赋值到此偏移地址,即a = 1。0x118c和0x28从哪来?这就是预先知道的偏移量啦,和函数调用类似的,这个偏移量就是当前地址与变量a的偏移。注意为了找到这个数据,我们花了一次函数调用,一次加法,和另一次隐含的加法。注意:“模块内部数据访问”实际没这么简单,还涉及到全局符号介入的问题。模块间数据访问由于动态链接时,只有在模块装载后才能知道其他模块中数据的地址,所以模块间数据访问就用到大名鼎鼎的全局偏移表GOT(Global Offset Table)了。简单来说,GOT位于数据段(.data段),保存本模块用到的外部符号(变量或函数)的实际地址;因为装载前不知道外部符号的实际地址,所以在装载时由动态链接器对每个模块的GOT进行更新设置真实地址。GOT在数据段中的位置是不变的。在编译时,本模块的所有对外部变量的访问(即需要知道外部变量地址的地方),都间接引用到GOT中的对应项;因为GOT的相对位置不变,所以可以像“模块内部数据访问”那样,编译时确定GOT的位置。由于GOT在数据段中,因此每个进程都可以自定义GOT内部的值,这样实现PIC。举个例子,本模块需要访问另一模块的变量b,那么获取b的地址的指令就变成了以相对寻址获取GOT中对应地址的值;这个值在装载时由动态链接器更新为b的实际地址,所以最终通过GOT获得了b的实际地址。由于改动只存在于数据段的GOT中,而代码段从编译到装载都没有发生变化,因此实现了地址无关,多个进程可以共用同一代码段。注意“模块间数据访问”需要用到“模块内部数据访问”。模块间调用或跳转“模块间调用或跳转”和“模块间数据访问”很类似,“模块间数据访问”得到的是外部变量的地址,用来读取或赋值;“模块间调用或跳转”则得到的是外部函数的地址,直接通过call进行跳转就好了。例如:call 494 &__i686.get_pc_thunk.cx&
add $0x118c, %ecx
movl 0xfffffffc(%ecx), %eax
call *(%eax)第一行得到PC值,第二和第三行添加偏移量,得到外部函数实际地址放到eax,最后间接调用eax所对应的函数。注意“模块间调用或跳转”需要用到“模块内部数据访问”。小结注意看模块间的数据访问和调用的代价,引入GOT后实现了PIC,但每次模块间的数据访问和调用都要首先计算出GOT的位置,然后才能通过GOT获得真实地址。DLL现在轮到主角DLL了,准确来说是PE DLL。DLL也用到了类似GOT的方法,称为导入地址数组(Import Address Table,IAT)。IAT和GOT非常类似,IAT中表项对应本模块中用到的外部符号的真实地址,初始为空(也不算为空),在装载后由动态链接器更新为真实地址。对于熟悉ELF的PIC的人来说,看到IAT了就很容易对应到GOT,然后认为PE里也是使用的和ELF类似的技术来实现PIC,这就犯了先入为主的错误啦!实际微软并没有采用ELF的那套PIC机制,就像最开始说的,DLL根本不是PIC。那么PE是怎么处理模块间调用或转跳的呢?这里只为了说明DLL不是地址无关,就只拿模块间函数调用举例了。模块间函数调用假设LibA是一个动态链接库,其源码中有对另一动态链接库LibB中函数add()的调用/* LibA.c */
#define DllExport
__declspec(dllexport)
#define DllImport
__declspec(dllimport)
DllImport int add(int a, int b);
DllExport int callLibBAdd(int a, int b){
return add(a, b);
}LibB中的add()就是/* LibB.c */
#define DllExport
__declspec(dllexport)
DllExport void add(int a, int b){
return a +
}现在将LibA.c编译为LibA.dll,将LibB.c编译为LibB.dll。那么LibA.c中的add(a, b)就被编译为了/* LibA.c add(a, b) */
CALL DWORD PTR [0x1000D11C]这个CALL DWORD PTR [0x1000D11C]意思就是间接调用0x1000D11C这个地址中保存的地址。而对于上面例子来说,其间接调用的地址就是此模块IAT中的某一项,即需要调用的外部函数在IAT中所对应的元素。如LibA.dll中,需要调用LibB.dll中的add函数,那么0x1000D11C正好对应add在LibA.dll的IAT中的位置。现在问题就来了:IAT的地址直接写死在代码段中,还能够实现地址无关吗?怎么动态链接呢?我们仔细对比DLL和ELF的“模块间函数调用”,ELF写死了当前地址与GOT的偏移值,然后通过一个小技巧获取到了当前地址(PC),与偏移值相加获得GOT的地址,再call调用外部函数,代码段不需要作任何修改;而DLL直接从一个认为是IAT的指定的地址(0x1000D11C)拿到一个值,以这个值作为外部函数地址进行CALL函数调用。我们很明显知道LibA.dll的位置是装载时确定的,也就是说LibA.dll中IAT的地址也是装载时才能确定的,那代码段里写个0x1000D11C是几个意思?除非LibA.dll就是这个DLL预先期望的位置,否则0x1000D11C不可能是IAT的位置!到这里我们就可以负责任地说DLL不是地址无关的了。理由很简单:如果装载后动态链接时0x1000D11C没有被修改为真正的IAT地址,那么程序是不能正常运行的;所以0x1000D11C会被修改,那么代码段就被修改了,不能再多个进程共享这个代码段了。那么是不是DLL不可能PIC了?根据上面的分析可以明确地说,只要寻找IAT地址还是用的绝对地址,就不可能实现PIC。到现在已经解释完了“DLL不是地址无关的”这句话,希望你也看明白了。如果不着急的话,下面继续介绍PE是怎么实现动态链接的,以及微软这么干的原因。装载时重定位上面只是说到了DLL动态装载时会找不到IAT,那么解决办法是什么呢?毕竟程序还是要运行的。这里要谈到PE里的另一个概念:基地址(Base Address)和相对地址(Relative Vitual Address,RVA)。PE文件在编译时,其模块内地址空间以预设的基地址为起始地址,而内部的相对地址都是相对于基地址的地址。一般来说,EXE文件基地址默认值为0x400000,DLL文件默认值为0x。以上述例子中的0x1000D11C为例,其基地址为0x,RVA为0xD11C。默认情况下,PE文件将被装载到预设的基地址,如上例默认会被装载到0x。想想如果真的0x这片区域是空闲的,LibA.dll被装载到了这里,那么0x1000D11C就真的对应到IAT了!装载后运行就不会出问题了!但是大家都知道,现实是这个默认地址很可能已经被其他的DLL占用了,现在冲突了,该怎么办呢?PE采用的就是装载时重定位的方法。在DLL装载是,如果目标地址被占用,那么操作系统就会为它分配一块新的空间,并且将DLL装载到该地址。这时基地址修改为新的装载地址,将模块内每个绝对地址引用都进行重定位。看着不可思议?虽然这个过程很浩大,但方法倒是挺简单的,就是简单的算术加减法。举个例子,对于一条编译时的指令MOV DWORD PTR [0x], 0x100假设0x1000100是模块中变量foo的地址,则其RAV为0x1000。如果装载时默认基地址0x被占用,则操作系统分配其一个新的基地址,假设为0x。因为现在0x已经不是正确的地址了,我们需要对这条指令重定位。由于绝对地址变化的只是基地址,RVA不变,因此只需要将新旧基地址的差值加上就好了;这里基地址差值为0x - 0x = 0x,所以新的绝对地址为0x + 0x = 0x,就是foo的地址。重定位后的指令为:MOV DWORD PTR [0x], 0x100我们也能够确信,现在这个DLL能够正常运行了。事实上,由于DLL内部的地址都是基于基地址的,或者是相对于基地址的RVA,那么所有需要重定位的地方只需要加上一个固定差值。这样的重定位过程又叫做重定基地址(Rebasing)。怎么重定位也讲完了,至此windows下的动态链接的主要问题都解决了,程序可以正常运行了。我们再回头看几个小问题:为什么之前举例是DLL调用DLL,而不是EXE调用DLL?很简单,你仔细看装载时重定位,需要重定位是因为默认基地址被占用,EXE作为第一个被装载的PE文件,0x400000上一片空白,用不着变换基地址,例子里也就不需要重定位,直接就能找到IAT。既然0x很容易被占用,可不可以编译时就指定其他基地址,省去重定位?当然可以,微软还提供工具这么干了,而且确实会有效果。是不是DLL代码段完全不能共享?我们说DLL不是地址无关,可从来没说DLL的代码段是一定不能共享的。想想修改代码段只是因为基地址变了,因此需要重定位所有的绝对地址,如果两个进程使用同一个DLL,如果基地址都是一样的是不是就可以公用一个了?当然可以!典型的例子是windows系统本身常用的DLL基地址都是精心调整过的,其装载时不需要进行重定位,系统内的所有进程都是共享的一份这些DLL代码段。微软为什么这么做我实在是没有找到官方对这方面的解释,下面都是结合《程序员的自我修养》和我自己猜测的。这么做缺点是显而易见的:浪费内存,每个进程都有一份DLL代码段的副本。而且还会有连锁反应,如页面交换,cache缓存等。但也不得不说这么做也有着难以比拟的优点。记得前面谈到ELF的PIC实现中经常说的注意点吗?就是想强调,为了实现PIC实际花了一些计算代价,最明显的,每次模块内部数据访问、模块间数据访问和模块间调用或跳转都要首先计算GOT的地址,这个计算涉及到一次函数调用和两次加法,注意是每次。反观DLL,在经过装载时重定位后,对这些访问或转跳都不需要任何计算,直接间接寻址就完了;换一种说法,一个DLL模块使用的次数越多,这个优点越明显。还有,DLL的装载时重定位所花的代价也是比较小的,就是简单的一次加法。同时我们也考虑windows本身的原因。众所周知,windows就是建立在DLL上的,windows内核实际上相当小;这样DLL就免不了频繁使用,也免不了经常的模块间数据访问和调用,采用装载时重定位是不是也有道理?另一方面就是一个忧伤的故事了,微软不能放弃的windows二进制兼容性,就连现在的windows 10也还能完全兼容95,这是微软说什么都不会放弃的,所以就算微软想把DLL改成PIC,恐怕也是力不从心,算是历史的包袱了。总结上面说了这么多也只是把PE和ELF比较有特点的地方拎出来讲,目的就是分清楚两者动态链接时的不同处理策略,以及出发点。两者的实现也各有优缺点,还希望不要一棒子打死,认为微软的设计跟不上时代。时间仓促,文中难免会有纰漏,还望不吝赐教。参考俞甲子, 潘爱民. 程序员的自我修养: 链接, 装载与库[M]. 电子工业出版社, 2009.
添加新评论}

我要回帖

更多关于 tablet pc怎么关闭 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信