我在测试代码时遇到了一个大问题。这个问题很烦人...... “超出最大递归深度”。看看它是如何工作的:
@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 - > a(第一次) - > 1 - > a(第二次) - > 1 - > a(第三次)等等 1 - > a(第八次) - > 1 - > a(ninith time),此时,您将获得“超出最大递归深度”。消息。如果你只是一直忽略这个消息,代码的内部计算将开始表现得很奇怪,甚至很难“显然”似乎已经产生了良好的结果
事实上,顺序并不重要。如果你在字母“a”和“b”之间或者“a”而不是“b”之间保持不同,则会发生同样的事情:1 - > b(第一次) - > 1 - > a(第二次) - > 1 - > a(第三次)...... 1 - > a(第八次) - > 1 - > b(第九次)。再次“超出最大递归深度”。消息
此外,如果用户无情地输入选项1(总是只停留在MAIN MENU),则会在第12次试用后弹出错误消息。
如何保持与上面相同的代码结构,但避免此错误?我在这里进行了简化,但我正在谈论一批数百行代码和几个二级,三级,四级等子菜单。
谢谢,
马莱克
答案 0 :(得分:10)
我在你发布的代码中没有看到任何递归,并且不足为奇,我无法让代码失败。
您必须在未显示导致问题的代码中进行递归。
我知道批处理文件有两种类型的致命递归错误:
1)SETLOCAL限制为最多32个级别。试图在没有发出ENDLOCAL的情况下第33次使用SETLOCAL将导致以下致命错误消息:
Maximum setlocal recursion level reached.
我怀疑这是你达到的递归限制。
更新: 实际上,每个CALL级别的限制为32 SETLOCAL。有关详细信息,请阅读http://ss64.org/viewtopic.php?id=1778
中的整个主题2)如果在任何CALL返回之前发出太多CALL,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 ******
可能有一些系统配置可能会增加你可以进行的递归CALL的数量,但总会有一些上限。
如果达到递归限制,那么除了重构代码之外,你真的没有太多选择。但除非你展示你的递归代码,否则很难(就我而言几乎不可能)我们建议你解决问题的方法。
编辑以回应更新的问题和评论
以下是您需要遵循的一般规则:如果您有代码循环,则每个SETLOCAL必须与ENDLOCAL配对。循环可以由GOTO创建,也可以是FOR循环。
你正在大量使用GOTO循环。您在发出SETLOCAL之后成功识别出在执行循环返回的GOTO之前必须发出ENDLOCAL。在我看来你解决了这个问题。
最后一次编辑 - 使用CALL简化逻辑
在使用GOTO循环时,要准确跟踪必须发出ENDLOCAL的位置和次数,这很痛苦。如果你改用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.