为什么函数没有被html调用js函数?谢谢!

我定义的函数为什么没有被成功调用
&!DOCTYPE HTML&&html&&head&&meta http-equiv="Content-Type" content="text/ charset=utf-8"&&title&计时器&/title&&head&&script type="text/javascript"&
var num=0;
function startCount() {
document.getElementById('count').value=
num=num+1;
if(num&=5){
alert("计数结束!");
setTimeout("startCount()", 1000);
startCount();&/script&&/head&&body&&form&&input type="text" id="count" /&&/form&&/body&&/html&
我搜了“Cannot set property 'value' of null”,人家说“在执行这段代码的时候.DOM还没有生成,所以会出错.”、“你的代码是当页面HTML加载完js就会执行的。在DOM生成之前就已经执行了(HTML代码加载完不意味着DOM生成了)。DOM还没有生成,就去获取DOM里面的元素,当然也就获取不到了。”
我好像有点知道了^_^谢谢你给的这条错误信息
没关系,一起加油
写下你的评论...
我把“startCount();”换成“setTimeout(&startCount()&, 0);”,这样是能运行起来的,但是我不懂,为啥我不能直接调用startCount()函数。QAQ
写下你的评论...
写下你的评论...
摸摸 一起加油
写下你的评论...
写下你的评论...
Copyright (C)
All Rights Reserved | 京ICP备 号-2理解JavaScript的函数调用和this - 文章 - 伯乐在线
& 理解JavaScript的函数调用和this
多年以来,我看到了许多人对于JavaScript函数调用有很多困惑。特别是许多人会抱怨,”this”在函数调用中的语义是令人疑惑的。
在我看来,通过理解核心的函数调用的原始模型,并且去看一下在此基础之上的其他方式的函数调用(对原始调用的思想的抽取)可以消除这些困惑。实际上,ECMAScript 标准也是这么考虑的。在某些地方来看,这篇文章是标准的简化,但是二者的基本思想是一致的。
核心的原始函数调用方法
首先,让我们来看一下核心的函数调用原始模型,一个Function的call方法[1]。call方法相对比较直接。
1、取参数的第一个到最后一个组成一个参数列表(argList);
2、第一个参数是thisValue;
3、把this设置为thisValue同时argList作为它的参数列表来调用函数。
JavaScript
function hello(thing) {
console.log(this + " says hello " + thing);
hello.call("Yehuda", "world") //=& Yehuda says hello world
function hello(thing) {&&console.log(this + " says hello " + thing);}&hello.call("Yehuda", "world") //=& Yehuda says hello world
正如你所见,我们调用了hello函数,把this设置为”Yehuda” 并传入了一个参数”world”。这是JavaScript函数调用的主要原始形式。你可以把所有其他的函数调用作为这个原始模式的运用来考虑。(要“运用”原始模型来调用其他函数就要用更便利的语法并依据一个更基本的主要原始模型)
注:[1]在ES5标准中,call方法的描述基于其他的,更低水平的基元,但是它是在那个基元基础上的非常简单的包裹,因此我在这里将其简化了。想了解更多可以参考这篇文章后面的信息。
简单的函数调用
很明显,总是用call来调用函数是令人难以忍受的。JavaScript允许我们用括号语法来直接调用函数(hello(“world”))。当我们这么做的时候,调用是这样的:
JavaScript
function hello(thing) {
console.log("Hello " +thing);
hello("world")
// desugars to:
hello.call(window, "world");
function hello(thing) {&&console.log("Hello " +thing);}&// this:hello("world")&// desugars to:hello.call(window, "world");
在ECMAScript 5 中,在严格模式下这个行为已经发生了变化[2]:
JavaScript
hello("world")
// desugars to:
hello.call(undefined, "world");
// this:hello("world")&// desugars to:hello.call(undefined, "world");
简短的一个版本说明是:一个函数调用比如:fn(…args)与fn.call(window [ES5-strict: undefined], …args)是一样的。
注意,对于行内的函数声明(function() {})() 与(function() {}).call(window [ES5-strict: undefined)也是一样的。
注:[2] 实际上,我撒了点谎。ECMAScript 5 标准说undefined(几乎)总是被传入,当不在严格模式下时,被调用的函数应该改变this的值为全局对象。这允许严格模式的调用者避免打破已经存在的非严格模式库。
下面一种非常常用的函数调用方式是函数作为一个对象的方法成员来调用(person.hello())。这种情况下函数调用像这样:
JavaScript
var person = {
name: "Brendan Eich",
hello: function(thing) {
console.log(this + " says hello " + thing);
person.hello("world")
// desugars to this:
person.hello.call(person, "world");
123456789101112
var person = {&&name: "Brendan Eich",&&hello: function(thing) {&&&&console.log(this + " says hello " + thing);&&}}&// this:person.hello("world")&// desugars to this:person.hello.call(person, "world");
注意,这和hello方法以这种形式附加到对象之后会变得怎样是无关的。记住,我们之前定义hello为一个独立的函数。让我们来看看动态的把函数附加到对象上发生了什么:
JavaScript
function hello(thing) {
console.log(this + " says hello " + thing);
person = { name: "Brendan Eich" }
person.hello =
person.hello("world") // still desugars to person.hello.call(person, "world")
hello("world") // "[object DOMWindow]world"
12345678910
function hello(thing) {&&console.log(this + " says hello " + thing);}&person = { name: "Brendan Eich" }person.hello =hello;&person.hello("world") // still desugars to person.hello.call(person, "world")&hello("world") // "[object DOMWindow]world"
注意,函数并没有”this”的一个持久的概念。他总是在被调用的时候基于调用者调用它的方式被设置。
应用Function.prototype.bind
由于对一个拥有持久的this的值的函数的引用有时候是非常方便的,历史上人们用了一个闭包把戏把一个函数转化为了拥有不变的this值:
JavaScript
var person = {
name: "Brendan Eich",
hello: function(thing) {
console.log(this.name + " says hello " + thing);
var boundHello = function(thing) { return person.hello.call(person, thing); }
boundHello("world");
12345678910
var person = {&&name: "Brendan Eich",&&hello: function(thing) {&&&&console.log(this.name + " says hello " + thing);&&}}&var boundHello = function(thing) { return person.hello.call(person, thing); }&boundHello("world");
尽管我们的boundHello 方法仍然可以改写为boundHello.call(window, “world”) ,我们转换了一个角度,应用我们的基元call方法来改变this为我们期望的值。
我们可以用自制体系来使得这个窍门有一般用途:
JavaScript
var bind = function(func, thisValue) {
return function() {
return func.apply(thisValue, arguments);
var boundHello = bind(person.hello, person);
boundHello("world") // "Brendan Eich says hello world"
var bind = function(func, thisValue) {&&return function() {&&&&return func.apply(thisValue, arguments);&&}}&var boundHello = bind(person.hello, person);boundHello("world") // "Brendan Eich says hello world"
为了理解上面的代码,你只需要两个额外的信息。首先,arguments是一个类数组对象,它拥有传到函数里的所有参数的引用。第二,apply方法的工作机制和基元call是完全一样的,唯一的不同是它采用的一个类数组的对象来作为参数,而不是用参数列表。
我们的 bind方法简单的返回一个新函数。当它被调用的时候,我们的新函数简单的调用传进来的原始函数,设置原始值为this。它也遍历参数。
因为this在某种程度上是一个常见的习语,ES5引入了一个新的bind方法给所有的Function对象来实现下面的行为:
JavaScript
var boundHello = person.hello.bind(person);
boundHello("world") // "Brendan Eich says hello world"
var boundHello = person.hello.bind(person);boundHello("world") // "Brendan Eich says hello world"
当你需要一个未加工的函数作为回调函数的时候这是非常有用的:
JavaScript
var person = {
name: "Alex Russell",
hello: function() { console.log(this.name + " says hello world"); }
$("#some-div").click(person.hello.bind(person));
// when the div is clicked, "Alex Russell says hello world" is printed
var person = {&&name: "Alex Russell",&&hello: function() { console.log(this.name + " says hello world"); }}&$("#some-div").click(person.hello.bind(person));&// when the div is clicked, "Alex Russell says hello world" is printed
当然,这个实现有点笨重,而且TC39(负责ECMAScript下一个版本的委员会)正在实现一个更加优雅的且向后兼容的解决方案。
jQuery里面的bind
因为jQuery里面大量的应用匿名回调函数,它内部使用call方法来设置那些回调函数的this值为更有用的值。比如,在所有的事件处理器函数中,jQuery没有接收window作为this的值(如果你没有特殊的干预),而是对元素调用call方法,并将事件处理器函数作为第一个参数。
这极其有用,因为在匿名函数内部的this的默认值并不是特别有用,但是它会给JavaScript初学者一个这样的感觉:this一般是很奇怪的,并且是难以推测的经常变化的一个概念。
如果你理解了从一个有语法糖的函数调用到抽取出了“糖分”的函数调用func.call(thisValue, …args)的基本转换规则,你应该就能操纵这个并不是十分“阴险”的 JavaScript this 值这一领域。
附:我有所‘欺骗’
在几个地方,对于规范的措辞我有所简化。或许最重要的‘欺骗’是我将func.call称为一个基元(”primitive”)。实际上,这个规范有一个基元(在内部被称为[[Call]])为func.call和obj.]func()所共有。
然而,让我们来看一下func.call的定义:
1、如果IsCallable(func) 结果为false,那么就抛出一个类型异常;
2、让 argList
为一个空列表;
3、如果这个方法被调用的时候参数不止一个,那么从左到右开始将arg1追加每一个参数作为 argList 的最新元素;
4、返回调用func的内部方法[[Call]]的执行结果,提供thisArg作为this的值,argList作为参数的列表。
正如你所见,这个定义本质上是一个很简单的JavaScript的语言绑定到基元[[Call]]操作符。
如果你看一下函数调用的定义,前七步是设置thisValue和argList,最后一步是:“返回 调用func的内部方法 [[Call]]的结果值,提供thisArg作为this的值,argList作为参数的列表”。
一旦thisValue和argList的值被确定,func.call的定义和函数调用的定义本质上是相同的字眼。
我在称call为一个基元上做了一点欺骗,但是在本质上他们意思还是一样的,我在文章开头拿出规范且做了引用。
还有很多案例(大多数文章会明显的包含with)我没有在文章中进行讨论。
打赏支持我翻译更多好文章,谢谢!
打赏支持我翻译更多好文章,谢谢!
任选一种支付方式
关于作者:
可能感兴趣的话题
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2017 伯乐在线新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
白手起家, 积分 15, 距离下一级还需 185 积分
论坛徽章:0
OwnWaterloo 发表于
回复 17# donet8
我不知道这是不是实现pic的唯一方式,总之这可以实现pic……
感谢大侠的精彩解释,我看懂了其中的一部分,还有一部分比较晕。所以我不得不以提问的方式来看我的理解是不是正确?
(1) 你是不是说,windows平台根本就没有PIC一说?(Linux平台才有)? 所以弄了个know dll
(2) 如果windows平台没有PIC. 那么你在17L的解释,是针对i386而言的呢,还是针对windows而言的?
(3) 你是不是说i386不直接支持PIC这样的功能,所以需要多条指令来实现,但是x64已经在这方面有很大的改进了?
(4) GCC编出来的东西才能指定有没有PIC内容? VC编出来的东西的绝对没有PIC内容?
(5) GCC编出来的东西如果有PIC的内容,好处只是减少装载时的开销,对于调用时的开销有影响么? 听你之前两帖的解释,好像也能减少运行时的开销。我不确定。
还请高人你指点一下!
论坛徽章:2
&& (3) 你是不是说i386不直接支持PIC这样的功能,所以需要多条指令来实现,但是x64已经在这方面有很大的改进了?
i386访问(同一个链接目标linking target)内的函数只需要一条指令就可以做到位置无关。 貌似机器码是0xe8(call), 0xe9(jmp)。
而且也是最常用的调用指令。
但位置无关地访问变量就需要好几条指令了……
x64有相对程序指针访问数据的质量 —— 我不知道是哪条, wiki这么说的 —— 于是位置无关地访问变量也应该只需要一条指令。
&& (2) 如果windows平台没有PIC. 那么你在17L的解释,是针对i386而言的呢,还是针对windows而言的?
如上, i386也是可以做到PIC的, 可以通过指令组合出这样的功能。
windows(i386)的问题是没有工具支持。 手动去PIC代价太大了……&&从实践上来说, 这就等于i386上的widnows没有PIC了……
可以通过一些手段减少重定位带来的页拷贝, 比如不使用__declspec(dllimport) , 这已经可以消除很多页拷贝了。
但要消除到linux那样就很麻烦了。
&& (4) GCC编出来的东西才能指定有没有PIC内容? VC编出来的东西的绝对没有PIC内容?
gcc 在windows上也不能指定pic……& &linux上才可以使用 -fpic 选项。
VC在i386上没有类似的选项。
但在x64上有, 肯定有, 我看过msdn那个页面, 但具体是什么选项我忘了……
&& (1) 你是不是说,windows平台根本就没有PIC一说?(Linux平台才有)? 所以弄了个know dll
如上, windows在i386上缺乏支持PIC的工具。 手工可以弄, 但代价太高…… windows在x64上应该就有pic了。
这与known dll貌似没什么关系……
&& (5) GCC编出来的东西如果有PIC的内容,好处只是减少装载时的开销,对于调用时的开销有影响么? 听你之前两帖的解释,好像也能减少运行时的开销。我不确定。
PIC可以减少装载开销, 减少页拷贝。 但一般来说在i386上会加大运行时开销。
不过因为有虚拟内存这回事, 空间就不一定总是能换到时间了……&&所以PIC与重定位代码的效率没有绝对的比较……
白手起家, 积分 15, 距离下一级还需 185 积分
论坛徽章:0
本帖最后由 donet8 于
16:33 编辑
OwnWaterloo 发表于
回复 21# donet8
&& (3) 你是不是说i386不直接支持PIC这样的功能,所以需要多条指令来实现,但是x64已经 ...
谢谢,您的这些解释都很清楚,我还剩2点没有看明白的,就是你说的:
&比如不使用__declspec(dllimport) , 这已经可以消除很多页拷贝了。&
(1)windows上面不使用__declspec(dllimport)就不能导出函数吧,你说的不使用指的是什么?
(2)我不知道为什么有了-fPIC就能减少页拷贝?
我的理解是这样的,linux平台上,一个.so文件能被两个可执行文件使用,在内存中只有一个拷贝,因为OS可以把dll加载到内存,然后通过PIC信息映射到两个进程的不同地址。因为PIC包含了一个跳转信息,所以一份拷贝足够。
而windows下面,假如有2个进程都要载入同一个dll的话,那么OS需要把dll复制两份,分别放到进程的地址空间里面去,因为没有PIC信息。
*****************
不知道我的理解是不是和你一致呢? 请斧正
论坛徽章:2
&& (1)windows上面不使用__declspec(dllimport)就不能导出函数吧,你说的不使用指的是什么?
导出必须__declspec(dllexport) 或者写def文件, 或者给链接器传递参数 —— 都是一回事。
但导入其实不需要……
每一个导出的函数/变量在导入库(import library,就是那个很小的.lib文件)都对应了一个符号, 有_imp__前缀(只说__cdecl调用约定……), 该符号其实是个指针。
使用dll导出的函数/变量,只要1) 在头文件里将它们声明为指针 2) 附加_imp__前缀, 就可以用了。
就像你在1楼发现的那样, 它们都多了一层间接性, 这间接性就可以解决链接时dll加载位置未知的问题。
__declspec(dllimport) 就是自动帮你完成这个事……&&用它声明的东西,其实是声明了一个名字有_imp__前缀的指针。
但每一个导出的函数还有另外一个符号,没有_imp__前缀。 该符号其实是一小段函数, 调用对应的_imp__函数指针。
如果没有__declspec(dllimport)&&只是直接声明函数,不是指针,也没有_imp__前缀, 对这些声明的调用就会reslove到这些短小的函数上。
&& (2)我不知道为什么有了-fPIC就能减少页拷贝?
&& 我的理解是这样的,linux平台上,一个.so文件能被两个可执行文件使用,在内存中只有一个拷贝,因为OS可以把dll加载到内存,然后通过PIC信息映射到两个进程的不同地址。因为PIC包含了一个跳转信息,所以一份拷贝足够。
&& 而windows下面,假如有2个进程都要载入同一个dll的话,那么OS需要把dll复制两份,分别放到进程的地址空间里面去,因为没有PIC信息。
dll/so能减少内存使用是建立在dll/so大部分内容对所有进程都相同的前提下,于是可以共享这些内容相同的部分。
即使是so, 里面的数据也不会被共享。
dll肯定是可以共享一部分的, windows还没有那么搓……
比如kernel32.dll, 它只能加载到首选基地址, 绝对不会发生重定位。 于是它的内容对所有进程都是相同的。
无论多少个进程, 它们的地址空间中属于 kernel32.dll 的那段, 全都可以映射到同一块物理内存上。
整个kernel32.dll(还有类似几个dll)都可以被共享。
但更多其他dll就没有这个特权了……
例如一个没有这种特权的a.dll, 导入了b.dll中的f函数。
如果它没有用__declspec(dllimport), 那么a.dll中对f的所有调用点都不需要重定位, 都有被共享的机会。
只有_f中对_imp__f的调用, a.dll 在链接时会假设它被加载到某个位置, 从而计算出 _imp__f 的地址, 产生的代码就是一个重定位项。
如果a.dll没有被加载到这个首选基地址, 产生的代码就是错误的, 需要重新修正 —— 内容就不同了, 就无法被共享。
这都还好, 因为_f这样的函数都很小, 而且会很集中, a.dll还是有很多内容有共享的机会。
而实践中更加推荐的是使用 __declspec(dllimport), 于是a.dll中所有f的调用点都需要重定位……
共享是有粒度的, 一个页上只要被改了一个字节就会导致整个页被写拷贝。
这么一搞, 基本就没剩下多少可共享的东西了……
白手起家, 积分 15, 距离下一级还需 185 积分
论坛徽章:0
OwnWaterloo 发表于
回复 23# donet8
非常非常感谢,我看懂了大部分,还有些矛盾。
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
对于问题(1)的回答
1. 在24L首先说“导入其实不需要”,“如果没有__declspec(dllimport)&&只是直接声明函数,不是指针,也没有_imp__前缀, 对这些声明的调用就会reslove到这些短小的函数上。”
是不是说: 我在使用这个dll的工程里面,可以按平常的方法去声明这些函数,不一定要加__declspec(dllimport)前缀,一样可以用dll的导出函数,还减少了一次跳转。时间和空间都节省了。
2. 如果我在exe的工程里面加了__declspec(dllimport)前缀去声明这些dll里面的函数,是不是exe里面会包含你说的call f,f实际上是_imp_f,它只是跳转到真的_imp_function。
如果我不加,就是call _imp_真正的函数了。
那这么看来,在我的exe工程里面,不加__declspec(dllimport)前缀,是百利无一害的事情?
但是在结尾处你说到&而实践中更加推荐的是使用 __declspec(dllimport)&。我的错误在哪里?
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
对于问题(2)的回答:
我知道copy on write是一种广泛使用的概念。对于没有特权的a.dll,所有调用点都是没有跳转的,不需要重定位。但是问题是必须被加载到某个首选基地址(就像known dll一样)。那么装载器就要去修正所有call指令的调用地址,装载a.dll的速度会超慢,对么?
然后a.dll还调用了b.dll的f(),我没有看懂下面两句话:
-----------------------
如果a.dll没有被加载到这个首选基地址, 产生的代码就是错误的, 需要重新修正 —— 内容就不同了, 就无法被共享。
这都还好, 因为_f这样的函数都很小, 而且会很集中, a.dll还是有很多内容有共享的机会。
-----------------------
需要重新修正: 是a的call f指令需要被修正? 还是别的? f这样的函数都很小,很集中,是指b.dll导出跳表吧!
是不是说,不用__declspec(dllimport)导入a.dll,那么a.dll当中对b.dll的f调用,可能会导致装载失败? 还是说不可能失败,但是需要装载时做额外计算?
可是用了__declspec(dllimport)也是需要做额外计算的啊? 这两种额外的重新修正,有什么不同?
论坛徽章:2
本帖最后由 OwnWaterloo 于
19:19 编辑
比如b.c:
__declspec(dllexport)
__declspec(dllexport) int* f(void) { return &x; }
复制代码cl会产生一个.lib文件。(还有其他一些工具也可以产生这个文件,格式不复杂)。
它里面包含了3个符号:
1. _f与_imp__f
2. _x或_imp__x(我不记得是哪个了……)
如果b.h是手工添加指针并修改名字
extern int* (*_imp__f)(void);
extern int* _imp__x;
#define f (*_imp__f)
#define x (*_imp__x)
复制代码或者是用C++的引用:
extern &C& {
extern int* (&_imp__f)(void);
extern int& _imp__x;
#define f _imp__f
#define x _imp__x
}
复制代码或者用__declspec(dllimport)
__declspec(dllimport) int f(void);
__declspec(dllimport)
复制代码那么使用b.dll的a.c:
int f0(void) { return *f()+12; }
int f1(void) { return x + *f(); }
复制代码产生的a.dll 会包含 _imp__f 与 _imp__x 的符号。
都是指针类型。 a.dll加载后,这两个符号的值会被分别设置为 b.dll 中 f 与 x 的实际地址。
这样就解决了a.dll链接时 b.dll 加载位置不确定, 无法产生代码的问题。
a.dll 中的 _imp__f与_imp__x 符号位置是确定的, 可以产生将它们当作指针使用的代码。 而指针的值在加载时计算。
重定位项有3个:
1. f0 中对f 的调用, 会将 _imp__f 的地址作为操作码
2. f1 中对f 的调用以及 x 的访问会将 _imp__f 与 _imp__x 的地址作为操作码
而如果b.h是这样:
extern int* f(void);
extern int* _imp__x;
#define x (*_imp__x)
复制代码a.dll中也会包含 _imp__f, imp__x 符号, 加载时也会设置为 f , x 在 b.dll 中的地址 —— 这点与前面的b.h版本是相同的。
同时a.dll还会多包含 _f 符号。
_f符号也是在.lib文件中, 实现方式就是将_imp__f当作指针并jmp过去。
所以即使a.c里没有直接使用_imp__f的符号, 而只使用了 _f 符号, 也会因为_f符号使用了_imp__f导致它出现在a.dll中。
_f 不需要加载器操心。
而现在重定位项就只有两个:
1. 对f1中对x的访问, 变量没办法……
2. _f中对_imp__f的访问
f0,f1中对_f的访问不是重定位项,那条call本身就是位置无关的。
看上去只节省了一个重定位项,但其实不是:
1. 变量没办法…… 每个使用点都是一个重定位项, 但变量导出比函数导出的情况要少很多
2. 而函数的情况是每一个被使用的函数产生一个重定位项(_f 中的 _imp__f) vs 每个调用点产生一个重定位项 (f0,f1 中的 _imp__f)
而且 _f 这类由.lib带来的函数都很小, 也会被安排得很集中, 即使修改了也只是很有限的几个页。
但f0, f1 就比较分散了。
&& 是不是说: 我在使用这个dll的工程里面,可以按平常的方法去声明这些函数,不一定要加__declspec(dllimport)前缀,一样可以用dll的导出函数,还减少了一次跳转。时间和空间都节省了。
前面就有2种不使用__declspec(dllimport)的方式, 但它们都使用 _imp__f 符号。
后面还有另一种不使用 __declspec(dllimport)的方式, 它使用 _f 符号。
_imp__f, _f 都是 .lib 提供的。
使用_f 会多一次跳转。&&f0 -& _f -& _imp__f vs f0 -& _imp__f。
而且显然也会稍微多占用一点空间。
&& 那这么看来,在我的exe工程里面,不加__declspec(dllimport)前缀,是百利无一害的事情?
变量只能使用 _imp__x, 最省心的方式就是 __declspec(dllimport)。
使用 _f 每一个调用点都会多一次跳转(这是主要的损失),而且a.dll会包含 _f 函数(几个字节)。
但好处就是将重定位项限制在_f内对_imp__f的调用, 而不是将 _imp__f 散布在 a.dll 各处。
1. 某个dll中的每一个 _imp__f, _imp__x 都需要加载时被设置为正确的地址, 需要根据被引用的dll(例如b.dll)的位置修正
2. 该dll中每一个使用 _imp__f, _imp__x 的地方都是重定位项,需要根据自己的位置(比如a.dll) 修正
第1条貌似不叫重定位, 而且无论a.dll是否加载到首选基地址都会执行。
第2条叫重定位, 是在a.dll没有被加载到首选基地址才会执行。
如果自己手工在a.dll中为每一个_imp__f/_imp__x都实现一个f/x_函数并只使用它们:
extern int* (*_imp__f)(void);
extern int* _imp__x;
int* f(void) { return _imp__f(); }
int* x_(void) { return &_imp__x; }
#define x (*x_())
复制代码就可以将 _imp__f与_imp__x 的使用限制到处于同一个 dll 的 f与 x_ 函数中。
而同一个dll 中对f,x_ 的函数的调用是位置无关的, 不需要修正, 也不会引发写拷贝。
并且这样的f 函数就是 b.lib 中实现的版本。 只是 x_ 在 b.lib 中没有对应物。
富足长乐, 积分 5681, 距离下一级还需 2319 积分
论坛徽章:129
本帖最后由 shang2010 于
11:43 编辑
每次看到大牛就很兴奋
北京皓辰网域网络信息技术有限公司. 版权所有 京ICP证:060528号 北京市公安局海淀分局网监中心备案编号:
广播电视节目制作经营许可证(京) 字第1234号
中国互联网协会会员&&联系我们:
感谢所有关心和支持过ChinaUnix的朋友们
转载本站内容请注明原作者名及出处

我要回帖

更多关于 谢谢你没有爱上我 的文章

 

随机推荐