中国DOS联盟论坛

中国DOS联盟

-- 联合DOS 推动DOS 发展DOS --

联盟域名:www.cn-dos.net  论坛域名:www.cn-dos.net/forum
DOS,代表着自由开放与发展,我们努力起来,学习FreeDOS和Linux的自由开放与GNU精神,共同创造和发展美好的自由与GNU GPL世界吧!

游客:  注册 | 登录 | 命令行 | 会员 | 搜索 | 上传 | 帮助 »
中国DOS联盟论坛 » DOS批处理 & 脚本技术(批处理室) » 从堆栈的角度,可以完美的解释 延迟环境变量的机制
作者:
标题: 从堆栈的角度,可以完美的解释 延迟环境变量的机制 上一主题 | 下一主题
5yue5
初级用户





积分 69
发帖 34
注册 2009-2-8
状态 离线
『楼 主』:  从堆栈的角度,可以完美的解释 延迟环境变量的机制

-------我---对call  echo-----的研究-----------------------------------
1---call的标准用法
例子1:
  call :a
  goto:eof
:a
:echo 123
例子2:
call :a  123
goto:eof
:a
echo %1

把例子1,2合并,写成一种非标形式:
例子1------>call echo 123----回显123
例子2----->call echo %1---如果保存文件为nn.bat,输入nn 123 ,则回显123
例子3-----call echo %%i ---回显 i---说明脱掉了2个%号,call和echo个脱掉一个
所以 call echo 实际是调用子程序的简写,这样就很容易解释,call有延迟环境变量的功能,但是它并没有
开启延迟环境变量。只是调用了子程序的结果。
2-----call在for中的实例
for %%i in (0 1 2) do (
set t=%%i
echo %t%
)
由于bat是解释执行的,最后就只显示---->2

再看:
call :a
goto:eof
:a
set t=123
for %%i in (0 1 2) do (
set t=%%i
echo %t%
)

回显---》123 123 123,这是因为cmd执行的是解释程序。
改一改
call:a %t%
goto:eof
:a
set t=123
for %%i in (0 1 2) do (
set t=%%i
echo %1
)

这时call 调用子程序的参数,,回显----》2 2 2
上一句call调用的是for执行前的结果,这句调用的是for执行后的结果
由此,的结论:要调用for执行后的结果,应该调用子程序的参数。

再改一改上一句
@echo off
set t=123
for %%i in (0 1 2) do (
set t=%%i
call:a
:a
echo %t%
)
此时显示----》0 123 1 123 2 123这句说明了call可以让数据指针在for语句执行中,跟着for一起舞动,但是for执行完之后,它又回复到了
初始状态。call的这个功能相当于延迟环境变量的功能,
这句话没法控制不把t=123推入堆栈,无论你把goto:eof加到那里都不行。这是因为call这个支点会把前面的数据推入堆栈,这里call认为它前面的数据是t=123 ,call后就会自动把原来的数据从堆栈里面弹出,这样我们就看到了0 123 1 123 2 123这种数据的舞蹈。遗憾的是bat没有控制堆栈的语句。经过无数人的研究,发现了一种病态的写法来解决这个问题。
再改一改:

@echo off
set t=123
for %%i in (0 1 2) do (
set t=%%i
call echo %%t%%
)
此时回显-----》0 1 2
   原因如下:因为call执行的原理是把它前面的结果先推入堆栈,然后进入分支点,把分支点的内容执行完后,就从堆栈里面弹出保存的数据,然后回到call的下一个支点,继续前进。
  上面语句虽然是病态的,但是那个多加的%号,刚好然call麻醉了。多的那个%号,call认为,它推入堆栈的数据应该是从for开始的数据,for每循环一次的数据 ,call都把它推入堆栈,call执行完子程序后(就是这个病态写法里面的echo %%t%%),就会从堆栈里面弹出数据,然后执行它的下一句,就是进入for的下一次循环。 call语句中数据指针%t%始终指向的是存储单元t  ,for每次循环的值都是放在t存储单元的。从而就动态的看到了t值的变话。方法虽病态,这也是没办法的办法,随叫微软不弄个管理堆栈的命令。延迟环境变量利用的应该也是堆栈技术。setlocal enabledelayedexpansion这句话,就是把前面的数据推入到堆栈。endlocal 就是把推入堆栈的数据弹出来。%t% ,!t! 为数据指针,指向的是存储单元t,所以 call推入堆栈的数据是for的数据。



   000本论点 为我独创,如有不同看法,欢迎讨论。

[ Last edited by 5yue5 on 2009-3-1 at 04:44 ]

   此帖被 +10 点积分     点击查看详情   
评分人:【 HAT 分数: +2  时间:2009-3-1 04:43
评分人:【 yishanju 分数: +8  时间:2009-3-2 03:50


2009-3-1 04:33
查看资料  发送邮件  访问主页  发短消息 网志   编辑帖子  回复  引用回复
xzyx
初级用户





积分 50
发帖 19
注册 2008-2-9
状态 离线
『第 2 楼』:  

1---call的标准用法
例子1:
  call :a
  goto:eof
:a
:echo 123
例子2:
call :a  123
goto:eof
:a
echo %1

把例子1,2合并,写成一种非标形式:
例子1------>call echo 123----回显123
例子2----->call echo %1---如果保存文件为nn.bat,输入nn 123 ,则回显123
例子3-----call echo %%i ---回显 i---说明脱掉了2个%号,call和echo个脱掉一个

所以 call echo 实际是调用子程序的简写,这样就很容易解释,call有延迟环境变量的功能,但是它并没有
开启延迟环境变量。只是调用了子程序的结果。(没有这么简单)


2-----call在for中的实例
for %%i in (0 1 2) do (
set t=%%i
echo %t%
)
由于bat是解释执行的,最后就只显示---->2 (代码是否写错,正确的显示结果为空。)

再看:
call :a
goto:eof
:a
set t=123
for %%i in (0 1 2) do (
set t=%%i
echo %t%
)

回显--->123 123 123,这是因为cmd执行的是解释程序。

改一改
call:a %t%
goto:eof
:a
set t=123
for %%i in (0 1 2) do (
set t=%%i
echo %1
)
这时call 调用子程序的参数,,回显---->2 2 2 (代码是否写错,正确的显示结果为空。“调用子程序的参数”如何理解?)
上一句call调用的是for执行前的结果,这句调用的是for执行后的结果
由此,的结论:要调用for执行后的结果,应该调用子程序的参数。(不知所云)

再改一改上一句
@echo off
set t=123
for %%i in (0 1 2) do (
set t=%%i
call:a
:a
echo %t%
)
此时显示---->0 123 1 123 2 123这句说明了call可以让数据指针在for语句执行中,跟着for一起舞动,但是for执行完之后,它又回复到了
初始状态。call的这个功能相当于延迟环境变量的功能。
这句话没法控制不把t=123推入堆栈,无论你把goto:eof加到那里都不行。这是因为call这个支点会把前面的数据推入堆栈,这里call认为它前面的数据是t=123 ,call后就会自动

把原来的数据从堆栈里面弹出,这样我们就看到了0 123 1 123 2 123这种数据的舞蹈。遗憾的是bat没有控制堆栈的语句。经过无数人的研究,发现了一种病态的写法来解决这个

问题。

再改一改:

@echo off
set t=123
for %%i in (0 1 2) do (
set t=%%i
call echo %%t%%
)
此时回显----->0 1 2
   原因如下:因为call执行的原理是把它前面的结果先推入堆栈,然后进入分支点,把分支点的内容执行完后,就从堆栈里面弹出保存的数据,然后回到call的下一个支点,继续

前进。(call对数据的处理很可能是这样,但关键是“预处理”。)

  上面语句虽然是病态的,但是那个多加的%号,刚好然call麻醉了。多的那个%号,call认为,它推入堆栈的数据应该是从for开始的数据,for每循环一次的数据 ,call都把它推

入堆栈,call执行完子程序后(就是这个病态写法里面的echo %%t%%),就会从堆栈里面弹出数据,然后执行它的下一句,就是进入for的下一次循环。 call语句中数据指针%t%始

终指向的是存储单元t  ,for每次循环的值都是放在t存储单元的。从而就动态的看到了t值的变话。方法虽病态,这也是没办法的办法,随叫微软不弄个管理堆栈的命令。延迟环

境变量利用的应该也是堆栈技术。setlocal enabledelayedexpansion这句话,就是把前面的数据推入到堆栈。endlocal 就是把推入堆栈的数据弹出来。%t% ,!t! 为数据指针,指

向的是存储单元t,所以 call推入堆栈的数据是for的数据。(无法确定是否与压栈弹栈有关。)

(至于“延迟环境变量的机制”,由于有“预处理”现象的存在,变量的初值可能不会被保留,因此本人保留意见。)

   此帖被 +4 点积分        点击查看详情   
评分人:【 zqz0012005 分数: +4  时间:2009-3-2 21:17


2009-3-2 02:46
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
zqz0012005
中级用户




积分 297
发帖 135
注册 2006-10-21
状态 离线
『第 3 楼』:  

我只知道call的用法是“从批处理程序调用另一个批处理程序。”
至于call echo %%var%%,我一般都是把它理解成内存中有一个bat,它的内容是echo %var%(为什么是%var%而不是%%var%%,那就是所谓的“预处理”机制,参见verybat.org英雄xzyx的大作)。

有call当然有堆栈,但堆栈保存的是当前地址(现场保护),这点与其他语言的函数类似。调用批处理子程序结束后,从堆栈弹出当前地址指针,程序继续执行。向堆栈中圧入地址是系统完成的,至于call会不会向堆栈圧入数据,这个还有待研究。

setlocal enabledelayedexpansion延迟环境变量的机制,首先我们知道它的作用是告诉命令解释程序(一般是CMD.exe)在预处理时不要进行变量替换,在执行语句时才展开。系统要为变量分配地址,但不一定要用到堆栈吧。至于它是怎样告诉预处理时不要进行变量替换,以及系统是怎样处理的,这个也有待研究。(如果读者对这些概念很模糊,还是请参阅verybat.org英雄xzyx的大作)

xzyx指出的楼主的错误,的确是错误,不知楼主是怎么得出的。



hh.exe ntcmds.chm::/ntcmds.htm
2009-3-2 12:10
查看资料  发短消息 网志  OICQ (411976538)  编辑帖子  回复  引用回复
everest79
金牌会员

一叶枝头,万树皆春



积分 2564
发帖 1127
注册 2006-12-25
状态 离线
『第 4 楼』:  

CMD下的解释性语言与大多数此类语言一样,在批次按行执行之前,都是有预读半编译的过程,当然不同语言的具体处理方式不同,CMD在这方面很弱,它只将脚本文本读入内存,转义某些符号标记断点等,然后建立内存中文本与实际文本行的关联,再按实际文本行号将内存中的命令逐行再次发送到CMD处理
所以 在批处理中的call echo %%i%%在命令行下是call echo %i%
这不是call的特点,也不是for的特点,就是cmd的



49206C6F766520796F752067757973 54656C3A3133383238343036373837
2009-3-2 12:23
查看资料  发短消息 网志   编辑帖子  回复  引用回复
zqz0012005
中级用户




积分 297
发帖 135
注册 2006-10-21
状态 离线
『第 5 楼』:  

发言前还是先翻翻前辈的研究成果吧。
for中的echo %var%与call echo %%var%%
我们不要忘记预处理时是整句读取的,for复合语句在预处理时是被作为一句处理的。
当前%var%的值是在前面的语句中查找。
这也是set var=1&echo %var%为什么也得不到预期结果的原因。



hh.exe ntcmds.chm::/ntcmds.htm
2009-3-2 12:23
查看资料  发短消息 网志  OICQ (411976538)  编辑帖子  回复  引用回复
zqz0012005
中级用户




积分 297
发帖 135
注册 2006-10-21
状态 离线
『第 6 楼』:  

everest79竟然插我的队,我还没说完呢。呵呵。



hh.exe ntcmds.chm::/ntcmds.htm
2009-3-2 12:25
查看资料  发短消息 网志  OICQ (411976538)  编辑帖子  回复  引用回复
xzyx
初级用户





积分 50
发帖 19
注册 2008-2-9
状态 离线
『第 7 楼』:  

大刁也来了,呵呵,一起讨论……

2009-3-2 22:45
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
netbenton
银牌会员

批处理编程迷


积分 1916
发帖 752
注册 2008-12-28
来自 广西
状态 离线
『第 8 楼』:  

(不打开所谓的变量延迟)执行这个看看,结果有没有压栈。
for %%a in (1,2,3,4) do (set/a a=a+%%a&call echo.%%a%%)

我也是把情况理解为对%号的变量的预处理,而对不含%号变量时,是没有影响的。
开起了所谓的变量延迟,如果不用!号的话,一样是没有效果,
我理解为!号预处理时不扩展,用到时才去找变量的值,

预处理做了什么:
一、把%号内的变量扩展为变量的值,然后把值作为命令行的一部分来执行命令行。
二、把连续的每两个%号合为一个,单独一个%直接丢掉
三、对!号的变量定出其最终变量名,但不取值,如:
set a=k
!#%a%!在预处理时,不会所#%a%直接当成!号的变量名,而是预处理后的:#k为!号的变量名,这里其实进行了两次预处理,1扩展%a%,2定出!的变量名是#k

执行命令:
把剩下的%号当做为字符而不是变量了,把!号当成为变量,并取出值。
如:
call echo.#!a! 和 call echo %%a%%经预处理后,执行命令时实为:
call echo #!a! 和 call echo %a% call 后得到的命令为:
     echo  #k  和      echo %a%,而不是:echo #!a!  和 echo k
这时又再次进行预处理得到:
     echo  #k  和      echo k

[ Last edited by netbenton on 2009-3-3 at 00:18 ]



精简
[你的+我的+他的]=>[大家的]    个人网志   
2009-3-3 01:40
查看资料  发送邮件  发短消息 网志  OICQ (37659560)  编辑帖子  回复  引用回复
zqz0012005
中级用户




积分 297
发帖 135
注册 2006-10-21
状态 离线
『第 9 楼』:  

楼上的怎么变成谈预处理了。。。你这讲的多累哟,看的人也累。
预处理机制前人已经分析地很清楚了,去翻翻老贴(经典帖子合集或直接去bbs.verybat.org搜索xzyx)



hh.exe ntcmds.chm::/ntcmds.htm
2009-3-3 06:47
查看资料  发短消息 网志  OICQ (411976538)  编辑帖子  回复  引用回复
674116666
社区乞丐





积分 -12
发帖 6
注册 2009-4-2
状态 离线
『第 10 楼』:  

学习了

2009-4-8 09:09
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复

请注意:您目前尚未注册或登录,请您注册登录以使用论坛的各项功能,例如发表和回复帖子等。


可打印版本 | 推荐给朋友 | 订阅主题 | 收藏主题



论坛跳转: