找回密码
 点一下
查看: 5779|回复: 34

[Takes Bug][仅限1.24/1.24b]一个简单的关于方便理解"takes" "return" "=

[复制链接]
发表于 2009-11-22 14:21:14 | 显示全部楼层 |阅读模式
这个演示仅限于1.24和1.24b。

预计将不会在1.24c中适用。


关于1.24b下的I2H具体参照这帖。
http://bbs.islga.org/read-htm-tid-34861.html


1.24为了修正return bug,改动了jass的语法检测,但是在检测环节上出了毛病,结果没有正确检测“让所有选择肢都要有返回值”。
[jass]
function Int takes integer i returns integer
    return i
endfunction


function I2UHelper takes integer i returns unit
    call Int(i)
    if false then
        return null
    endif
endfunction
[/jass]
所以像这种东西,没有正确地检测出来而让其通过了。实际上I2UHelper这个函数没有返回值。一个正确的语法检测器其实至少得检测那个条件分支为else的时候是否有返回值。(其实最初的return bug依然是由于语法检测漏洞而导致的。)

这个函数的作用原理是这样。如果一个jass函数没有返回任何值就结束,那么它就会取上一个返回值作为自己的返回值。这个其实是由jass虚拟机的作用方式决定。

很显然,上一个返回值是i,于是到了这里就成了I2UHelper的返回值,并被自动转换为了unit型


但是单纯的使用I2UHelper这个函数是没用的。set u=I2UHelper(i)毫无作用。需要加一层嵌套

[jass]
function Unit takes unit u returns unit
    return u
endfunction

function I2U takes integer i returns unit
    return Unit(I2UHelper(i))
endfunction
[/jass]

原因是,单纯的等于号会判断最后一个有返回值的函数返回的值是否与自己需求的类型相匹配,如果相匹配,那么就赋值。

=号仅仅是被动地接受最后一个有返回值的函数提供的返回值,如果类型不同则不会予以转换,而是干脆不处理。

以下一个演示可以方便大家加深理解。


[jass]
function Int takes integer i returns integer
    return i
endfunction

function Unit takes unit u returns unit
    set udg_uTakes=u
    call DisplayTextToForce( GetPlayersAll(), "Takes:"+GetUnitName(udg_uTakes) )
    return u
endfunction

function I2UHelper takes integer i returns unit
    call Int(i)
    if false then
        return null
    endif
endfunction

function I2U takes integer i returns unit
    return Unit(I2UHelper(i))
endfunction

[/jass]

[trigger]
Initialization
    Events
        Map initialization
    Conditions
    Actions
        Set u = Blood Mage 0000 <gen>
        Set uReturn = Mountain King 0002 <gen>
        Set uTakes = Mountain King 0002 <gen>
        Set uEqu = Mountain King 0002 <gen>
        Custom script:   set udg_uReturn=I2U(GetHandleId(udg_u))
        Game - Display to (All players) the text: (Return: + (Name of uReturn))
        Custom script:   set udg_uEqu=I2UHelper(GetHandleId(udg_u))
        Game - Display to (All players) the text: (=: + (Name of uEqu))
[/trigger]

可以看到自定义脚本部分实现了1.24b下的"return" bug,(好吧其实我们应该叫它"Takes Bug")。

但不同的是,我在函数Unit()返回之前加了两条语句set udg_uTakes=u  和 call DisplayTextToForce( GetPlayersAll(), "Takes:"+GetUnitName(udg_uTakes) )用于在这个函数出现返回动作之前显示它takes动作获得的值。

地图运行后,分别会显示udg_uReturn,udg_uTakes,udg_uEqu三个变量经过运算后的值。
uReturn代表用=号被动接受与需求值数据类型相同的函数的返回值的结果。
uTakes代表用takes关键字主动获取与需求值数据类型不同的函数的返回值的结果。
uEqu代表用=号被动接受与需求值数据类型不同的函数的返回值的结果。


这三个变量都被初始化为指向一个山丘之王,如果三个变量都正确获得了返回值,那么屏幕上应该显示三个udg_u所指向的单位的名字即血法师。如果某个获得的返回值为空,那么屏幕上这个变量的对应值应该为null或不显示。如果某个变量什么变化都没有,那么屏幕上这个变量的对应值应该为它初始化所指向的单位名字即山丘之王。

猜猜看显示结果?



Takes:Blood Mage(血法师)
Return:Blood Mage(血法师)
=:Mountain King(山丘之王)


你猜对了吗?也许有些同学会认为那个"=:"后面应该是(null)或者干脆不显示吧?错了哦,其实=号这个东西完全属于被动接受型,如果后面的函数返回的类型和等号前的变量类型不匹配,它根本就什么都不会干,而不会得到一个空值。

所以

uEqu被动地接收I2UHelper()的返回值,而I2UHelper()没有返回值,所以取上一个返回值,即血法师的GetHandleId()后的handle值但是这个值的类型为integer,与uEqu的类型unit不匹配,所以它什么也没做。

uTakes由于主动向I2UHelper()获取返回值,而这个函数没有返回值,于是便取得上一个返回值,得到了血法师的GetHandleId()后的handle值,并将其变回unit类型。

uReturn被动地接收Unit(),而Unit()有unit类型的返回值,所以正确得到血法师的handle。

=ReturnTakes.w3x

17 KB, 下载次数: 36

发表于 2009-11-22 14:26:30 | 显示全部楼层
好神秘,竟然看懂了……
回复

使用道具 举报

发表于 2009-11-22 14:34:06 | 显示全部楼层
明白了。谢谢头目。
回复

使用道具 举报

 楼主| 发表于 2009-11-22 14:50:31 | 显示全部楼层
希望更深入的同学,可以研究下=号后面如果是复合表达式的情况,而不仅仅是一个单纯的函数。
回复

使用道具 举报

发表于 2009-11-22 14:59:06 | 显示全部楼层
[jass]
function I2UHelper takes integer i returns unit

    //call Int(i)

    if false then

        return null

    endif

endfunction
[/jass]
如上注释掉call Int(i)
效果还是一样的
那么完全跟它无关了

=ReturnTakes.w3x

17 KB, 下载次数: 7

回复

使用道具 举报

发表于 2009-11-22 15:04:25 | 显示全部楼层
恩,前面我也在我昨天那个演示里试过了,去掉那句完全无影响~~
回复

使用道具 举报

 楼主| 发表于 2009-11-22 15:11:09 | 显示全部楼层
引用第4楼疯人¢衰人于2009-11-22 14:59发表的  :
如上注释掉call Int(i)
效果还是一样的
那么完全跟它无关了



怎么会呢,仔细看我的介绍,最后一个返回值的意义。


你有没有发现这种情况下最后一个有返回值的函数是GetHandleId()?正好是血法的handle?

一个简单地例子就可以解决你的疑问。

[jass]
function I2UHelper takes integer i returns unit
    call CreateUnit(Player(0),'hpea',0,0,0)
    if false then
        return null
    endif
endfunction

[/jass]

你觉得加了这句  call CreateUnit(Player(0),'hpea',0,0,0) 对结果会造成什么影响?
回复

使用道具 举报

 楼主| 发表于 2009-11-22 15:19:46 | 显示全部楼层
或者你也可以修改Int()

变成这样

function Int takes integer i returns integer
    return i+1
endfunction

然后再看看效果。(注意保持山丘之王的handle为血法+1)


事实是,前一个例子返回农民的handle,后一个例子返回血法的handle+1,也就是山丘的handle。
回复

使用道具 举报

 楼主| 发表于 2009-11-22 15:34:10 | 显示全部楼层
另外我发现我的上面其实也有个错误。

=并不是完全消极的,并不是说目标函数一定要有return动作它才会得到值。

而是等号后面的返回值类型与需求类型不匹配的时候它不会做动作,更正一下。

因为如果这种情况。


function uuu takes nothing returns unit
    return udg_u
endfunction

function I2UHelper takes integer i returns unit
    call uuu()
    if false then
        return null
    endif
endfunction

那么set udg_uEqu=I2UHelper(GetHandleId(udg_u))是可以得到udg_u的,之前Int()的情况是因为Int的返回值是integer,而udg_uEqu是Unit,导致了类型不匹配。所以才会没有任何动作。

但是takes关键字会忽略类型不匹配的问题。所以才能得到值。


此处更正一下。
回复

使用道具 举报

发表于 2009-11-22 15:37:20 | 显示全部楼层
头目
我的感觉并非是call Int()
而是return

正因为GetHandleId自己也有个return
所以没那句也会成立

如果整个函数本身没有return
它会从调用当前函数的函数中找最后一个return
如果不是用GetHandleId()作为参数
而是将这个值作为参数的话
那么会返回空

不过具体使用哪个参数我这里搞不明白了
同样的函数返回值竟然不同

演示中多次按下左右即可


好吧这里我自己忘记重置了

=ReturnTakes.w3x

19 KB, 下载次数: 3

回复

使用道具 举报

 楼主| 发表于 2009-11-22 15:44:16 | 显示全部楼层
引用第9楼疯人¢衰人于2009-11-22 15:37发表的  :
头目
我的感觉并非是call Int()
而是return

正因为GetHandleId自己也有个return
.......


我一直都在说最后一个有返回值的函数的返回值

当然是在说return而不是call。

我哪里说call是关键了?而是call了一个有返回值的函数是关键。你看我给你的例子都是call有返回值的函数。
回复

使用道具 举报

发表于 2009-11-22 15:48:36 | 显示全部楼层
这回的演示对了
不过结果暂时分析的乱套了……

=ReturnTakes.w3x

19 KB, 下载次数: 7

回复

使用道具 举报

发表于 2009-11-22 15:54:04 | 显示全部楼层
我的表达有错误……

我的理解为
当一个没有返回的函数被调用并且需要返回时
它会在这个函数里从下至上的寻找最后一个执行的return
也就是当前进程中的上一个return
然后返回这个值

那么现在这个bug本身就可以这样猜测
return使用了一个公用空间
类似Unionbug的公用空间
每次return的值都会从这里读取
而这里与Unionbug一样是不检查类型的
于是从这里得到了RB的效果

并且,当当前函数没有return时
因为上次return没有被覆盖
那么会返回上次return的值
回复

使用道具 举报

 楼主| 发表于 2009-11-22 16:01:34 | 显示全部楼层
引用第12楼疯人¢衰人于2009-11-22 15:54发表的  :
我的表达有错误……

我的理解为
当一个没有返回的函数被调用并且需要返回时
它会在这个函数里从下至上的寻找最后一个执行的return
.......


这个就是我一直的说法。

不过补充一点。其实这里有一定的类型检测的。如果=号需求的类型与那个最后一个返回值类型不符,就不会取它。但是当函数用takes关键字来取它的时候,就会忽视类型而出现转换的效果。
回复

使用道具 举报

发表于 2009-11-22 16:11:21 | 显示全部楼层
测试了下
这样的返回有效需要这样的条件
1、需要当前的函数执行内有返回,比如
[jass]
function I2UHelper takes integer i returns unit
    call Int(i)
    if false then
        return null
    endif
endfunction
[/jass]
的Int(i)或者如果它不存在,那么给的参数里存在return也行
2、这个函数调用之前的return无效,只有在调用之后出现的return有用
3、无法再其中等待

[jass]
function I2UHelper takes integer i returns unit
    call Int(i)
    call TriggerSleepAction(1)
    if false then
        return null
    endif
endfunction
[/jass]
这样会导致游戏崩溃
回复

使用道具 举报

 楼主| 发表于 2009-11-22 16:17:06 | 显示全部楼层
那是因为等待的时候,当前函数会挂起,jass会去执行别的函数。

自然就让这个接力棒脱节了。我有意不拿Union Bug作类比的原因就是这个,因为它并不能说是“在当前函数体里有效”的。
回复

使用道具 举报

发表于 2009-11-22 16:20:34 | 显示全部楼层
总之这个bug不会有大范围使用的可能
另外这个bug也很快就会被堵上
所以基本上就是个了解

话说就我的理解能力不好……
其他人都直接看懂了……
回复

使用道具 举报

发表于 2009-11-22 16:35:39 | 显示全部楼层
124b真是个很土的玩意儿。。。

其实每条函数可以看成是一个虚拟机,带两个堆栈(参数堆栈和返回堆栈)。。。

- 研究暴雪的这种致命小bug只会让他不停的出补丁而已。
回复

使用道具 举报

发表于 2009-11-22 17:06:27 | 显示全部楼层
难道让玻璃渣重新写套jass系统?
回复

使用道具 举报

发表于 2009-11-22 19:14:15 | 显示全部楼层
反正离SC2只有一年了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 点一下

本版积分规则

Archiver|移动端|小黑屋|地精研究院

GMT+8, 2024-11-21 23:27 , Processed in 0.090190 second(s), 21 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表