2012-08-11 103 views
1

我在测试代码时遇到了一个大问题。而这个问题是令人讨厌的消息... “超出最大递归深度”。看看它是如何工作的:批量限制 - 浏览菜单时的最大递归

@echo off 

REM ---------- MAIN MENU ---------- 
:Main 

echo 1.Supermarket 
echo 2.Exit Batch 

setlocal 
for /f %%A in ('"prompt $H & echo on & for %%B in (1) do rem"') do set "BS=%%A" 
set /p Menu=%BS% Type in your option {1,2} followed by ENTER: 
if not '%Menu%'=='' set Menu=%Menu:~0,1% 
if %Menu% EQU 1 ENDLOCAL & goto Super 
if %Menu% EQU 2 goto EOF 

REM ---------- SECONDARY MENU ---------- 
:Super 

echo 1.Supermarket 
echo a.Apple 
echo b.Banana 

setlocal 
for /f %%A in ('"prompt $H & echo on & for %%B in (1) do rem"') do set "BS=%%A" 
set /p Menu=%BS% Type in your option {1,a,b} followed by ENTER: 
if not '%Menu%'=='' set Menu=%Menu:~0,1% 
if %Menu% EQU 1 ENDLOCAL & goto Main 
if %Menu% EQU a ENDLOCAL & goto App 
if %Menu% EQU b ENDLOCAL & goto Ban 

:App 

setlocal enabledelayedexpansion 
set /a cnt=0 
for /f "tokens=* delims= " %%a in (*.apple) do (set /a cnt+=1) 
if %cnt% EQU 0 (
echo There are %cnt% apples & pause>nul & ENDLOCAL & goto Main 
) else (
echo There are %cnt% apples 

[... do many things and after 200 lines] 

echo The total number of apples was %cnt% & pause>nul & ENDLOCAL & goto Main 

:Ban 
echo This is Banana & pause>nul & goto Main 

:EOF 

让我们假设用户按以下顺序执行一排:

1 - >(第一时间) - > 1 - >(第二次) - > 1 - > a(第三次)等等等等等012-1 - > a(第八次) - > 1 - > a(ninith时间),此时,您将获得“Maximum递归深度超出“。消息。如果你只是不理会这个消息,你的代码的内部计算将开始表现奇怪,甚至很难它“显然”似乎已经产生良好的结果

事实上,顺序并不重要。如果在字母“a”和“b”或更多“a”之间保持不同于“b”,则会发生同样的情况:

1→b(第一次)→1→a第二次)→1→a(第三次)... 1→a(第八次)→1→b(第九次)。 “超出最大递归深度”。消息

此外,如果用户无情地键入选项1(总是仅保留在主菜单),错误消息在第12次尝试后弹出。

如何保持与上述相同的代码结构但避免此错误?我在这里简化了,但我正在谈论一批数百行代码和几个二级,三级,四级等子菜单。

谢谢
Maleck

+0

我有很多setlocal打开但没有关闭。通过在代码的几个区域发出命令ENDLOCAL,问题几乎已经消失。 ENDLOCAL太多了吗?我不想错过任何像%cnt%这样的变量的结果。根据ENDLOCAL的去向,%cnt%不显示任何值。在数百行代码中,我应该在哪里放置ENDLOCAL以获得性能并且不会错过数据? – 2012-08-12 04:01:14

回答

9

我没有看到你发布的代码的任何递归,这并不奇怪,我不能让代码失败。

您必须在代码中递归,而您没有显示导致问题。

我知道两种类型的致命错误递归的用批处理文件:

1)SETLOCAL被限制为最高32级。尝试使用SETLOCAL第33个时不发出ENDLOCAL会导致以下致命错误消息:

Maximum setlocal recursion level reached.

我怀疑这是你达到递归限制。

UPDATE:实际上,每个CALL级别的限制是32个SETLOCAL。欲了解更多信息,请阅读整个线程http://ss64.org/viewtopic.php?id=1778

2)CALL递归如果您发出呼叫太多之前的任何人返回可能会失败。只要到达脚本末尾,EXIT/B或GOTO:EOF,批处理就会从CALL返回。递归CALL的最大数目取决于机器。 (也许代码依赖?)错误消息将是这个样子:

****** B A T C H R E C U R S I O N exceeds STACK limits ****** 
Recursion Count=600, Stack Usage=90 percent 
******  B A T C H PROCESSING IS A B O R T E D  ****** 

可能有一些系统配置,可以增加递归调用就可以使的数量,但总会有一些上限。


如果您达到了递归限制,那么除了重构代码之外,您确实没有太多选择。但是除非你显示你的递归代码,否则我们很难为你提供一种解决这个问题的方法(几乎不可能)。


编辑回应更新的问题和评论

这里是您需要按照一般的规则:如果你有一个代码回路然后每SETLOCAL必须以ENDLOCAL配对。循环可以由GOTO创建,也可以是FOR循环。

您正在大量使用GOTO循环。您在发出SETLOCAL后成功识别出您必须发出ENDLOCAL,然后才能执行循环返回的GOTO。它看起来像你解决了这个问题。


进行最后一次编辑 - 使用CALL来简化逻辑

这是跟踪的确切位置和多少次,你必须发出ENDLOCAL当你用GOTO循环中的疼痛。如果你使用CALL,逻辑就简单得多,代码也更加结构化。

一旦例程结束,所有在CALLed子例程执行期间发出的SETLOCAL语句都会隐式结束。使用CALL时不需要明确使用ENDLOCAL。

下面我已经展示了如何重构你的代码来使用CALL。研究所有的代码 - 我已经做了许多细微的改变,我认为它们可以简化和/或改进逻辑的其他方面。

@echo off 
setlocal 
:: Define the BS variable just once at the beginning 
for /f %%A in ('"prompt $H & echo on & for %%B in (1) do rem"') do set "BS=%%A" 

:Main ---------- MAIN MENU ---------- 
:: ECHO(echoes a blank line 
echo(
echo Main Menu 
echo 1.Supermarket 
echo 2.Exit Batch 
:: If user hits ENTER without entering anything then current value remains. 
:: So clear the value to make sure we don't accidently take action based 
:: on prior value. 
set "Menu=" 
set /p Menu=%BS% Type in your option {1,2} followed by ENTER: 
if defined Menu set "Menu=%Menu:~0,1%" 
if "%Menu%" EQU "1" call :Super 
if "%Menu%" EQU "2" exit /b 
goto :Main 

:Super ---------- SECONDARY MENU ---------- 
setlocal 
echo(
echo Supermarket 
echo a.Apple 
echo b.Banana 
echo 1.Retutn to Main 
set "Menu=" 
set /p Menu=%BS% Type in your option {1,a,b} followed by ENTER: 
if defined Menu set "Menu=%Menu:~0,1%" 
:: Use IF /I option so case does not matter 
if /i "%Menu%" EQU "1" exit /b 
:: Note that :App and :Ban are still logially part of this subroutine 
:: because I used GOTO instead of CALL. When either :App or :Ban ends, then 
:: control will return to the Main menu 
if /i "%Menu%" EQU "a" goto :App 
if /i "%Menu%" EQU "b" goto :Ban 
goto :Super 

:App 
setlocal enableDelayedExpansion 
set /a cnt=0 
for /f "tokens=* delims= " %%a in (*.apple) do (set /a cnt+=1) 
echo There are %cnt% apples 
if %cnt% GTR 0 (
REM ... do many things and after 200 lines 
echo The total number of apples was %cnt% 
) 
pause>nul 
exit /b 
:: The EXIT /B above returns to the Main menu (ends the CALL to :Super) 
:: Note that the SETLOCAL at the start of :Super and the one at the 
:: start of :App are implicitly ended. No need to explictly issue 
:: ENDLOCAL. 

:Ban 
echo This is Banana 
pause>nul 
exit /b 
:: The EXIT /B above returns to the Main menu (ends the CALL to :Super) 
:: Note that the SETLOCAL at the start of :Super is implicitly ended. 
:: No need to explictly issue ENDLOCAL. 
+0

@dbnham:添加了代码编辑和评论。见上面 – 2012-08-12 04:04:18

+0

@LuizMaleck - 看起来像你解决了你的问题。看到我编辑的答案。 – dbenham 2012-08-12 15:00:49

+0

@dbnham:再次感谢您的亲爱的朋友!亲切的问候。 – 2012-08-12 15:54:49