2

コードのテスト中に大きな問題に遭遇しました。そして、この問題は厄介なメッセージです... 「最大再帰深度を超えました」。それがどのように機能するか見てください:

@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回目) --> 1 --> a (2回目) --> 1 --> a (3回目) etc etc etc 1 --> a (8回目) --> 1 - -> a (9 回目) この時点で、「最大再帰深度を超えました」が表示されます。メッセージ。このメッセージを無視し続けると、コードの内部計算がおかしくなり始めます。「明らかに」良い結果が得られているように見えても、

実際、順序は関係ありません。次のように、文字 "a" と "b" の間、または "a" よりも "b" の間で変化し続けると、同じことが起こります。

1 --> b (1回目) --> 1 --> a (2回目) --> 1 --> a (3回目) … 1 --> a (8回目) --> 1 - →b(9回目)。再び「最大再帰深度を超えました」。メッセージ

さらに、ユーザーが執拗にオプション 1 を入力すると (常に MAIN MENU のみに留まる)、12 回目の試行後にエラー メッセージが表示されます。

上記と同じコード構造を維持しながら、このエラーを回避するにはどうすればよいですか? ここでは単純化していますが、数百行のコードのバッチと、いくつかの二次、三次、四次などのサブメニューについて話しているのです。

ありがとう、
マレック

4

1 に答える 1

12

あなたが投稿したコードには再帰が見られません。驚くことではありませんが、コードを失敗させることはできません。

問題の原因を示していないコードに再帰が含まれている必要があります。

バッチ ファイルで発生する致命的な再帰エラーには、次の 2 種類があります。

1) SETLOCAL は最大 32 レベルに制限されています。ENDLOCAL を発行せずに SETLOCAL を 33 回使用しようとすると、次の致命的なエラー メッセージが表示されます。

Maximum setlocal recursion level reached.

これがあなたが到達している再帰の限界だと思います。

更新: 実際には、上限は CALL レベルごとに 32 SETLOCAL です。詳細については、http://ss64.org/viewtopic.php?id=1778のスレッド全体をお読みください。

2) いずれかが戻る前に CALL を発行しすぎると、CALL 再帰が失敗する可能性があります。バッチは、スクリプトの終わり、EXIT /B、または GOTO :EOF に到達するたびに、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 を発行した後、ループバックする GOTO を実行する前に ENDLOCAL を発行する必要があることを確認しました。あなたがその問題を解決したように私には見えます。


最後の編集 - CALL を使用してロジックを簡素化

GOTO でループするときに ENDLOCAL を発行する必要がある場所と回数を正確に追跡するのは面倒です。代わりに CALL を使用すると、ロジックがはるかに単純になり、コードもより構造化されます。

CALL されたサブルーチンの実行中に発行されたすべての 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.
于 2012-08-11T21:26:47.220 に答える