2012-08-11 102 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