GOTOの禁断の果実は甘い(マイクロコントローラーのバージョン)!

良い一日!

C / C ++のgoto演算子に対するあなたの態度は何ですか? ほとんどの場合、プログラミングを学んだときにそれを使用しました。 それからあなたはそれが悪いことを知り、それを忘れました。 複雑なエラー処理を伴うこともありますが、いや、いや、試してみてください... throw ... catch。 またはネストされたループを終了するには...いいえ、フラグと多くの困難があります。 または、ネストされたスイッチが...いいえ、いいえ、いいえ、同じフラグがある場合。
それでも、夜の静寂の中で、あなたは潜在意識に罪深い思考を許しました-「 ここで gotoを使用しないのはなぜですか? そして、このプログラムはよりスリムに見えるようで、最適な形で登場します。 はい、それは良いでしょう...しかし、いいえ- それは不可能です 、彼らは忘れていました!」
なぜそうですか?
長年の実践とさまざまなプラットフォームに基づいた小さな調査と、この問題に対する私の姿勢です。 この記事は、 C ++の場合と同じものですが、Cおよびマイクロコントローラー専用のハイライトです。

gotoの熱烈な反対者へのリクエスト-私がこのトピックを提起し、gotoの部分的な支持者であるという理由だけで、私をマイナスカルマの地獄の火に倒さないでください!

歴史的小旅行


組み合わせ回路、メモリ付き回路が何であるか、そしてアセンブラーがこれからどのように成長したかを完全によく知っている人のために、私たちは安全に結論にスキップすることができます。

そして、それはすべて組み合わせ回路から始まりました



最初は言葉でした-そして言葉は機能でした。 それが論理変数のブール関数であることはそれほど重要ではありません-それに基づいて、彼らはすべての(ほぼ)数学、そしてテキスト、グラフィックスを実装することに成功しました...とにかく、コンピュータ技術を使用すると非常に便利であることが判明しました算術、次に三角法およびその他のアクションを実行し、変数の関数の値を見つけます。

つまり、変数の値によって関数の値を見つけるデバイスを作成する必要がありました。

この困難な問題を解決するために、算術演算を実行するための逐次アルゴリズムが構築されました(このようなアルゴリズムで計算の精度が一定の場合、各算術演算は1クロックサイクルで実行できます)。

アルゴリズムを使用することで、組み合わせ回路を構築することは難しくありません-即座に(論理デバイスの動作と信号の伝播時間まで)出力で答えを出す回路。
問題は、ここで移行が必要ですか? いいえ、彼らは単にそこにいません。 一貫した行動方針があります。 最終的に、これらのすべてのアクションは単一のクロックサイクルで実装できます(私は主張しませんが、非常に面倒になりますが、すべてのデータのビット深度を考えると、学生はVHDLまたはVerilogのシンセサイザーは言うまでもなく、そのようなスキームを構築します)

しかし、その後、メモリ回路が介入しました



そして、誰かのスマートヘッドがフィードバック回路(RSトリガーなど)を思いつきました。 そして回路の状態が現れました。 そして、状態はメモリを持つすべての要素の現在の値に他なりません。

このようなメモリエレメントの出現により、ハードワイヤードデバイスからファームウェアへの革新的な進歩が可能になりました。 簡単に言うと、ファームウェアにはコマンドメモリがあります。 現在のファームウェア(加算、減算、またはその他)を実装する別のデバイスがあります。 しかし、別のデバイスが「現在の」ファームウェアの選択に関与しています-それを「サンプリングデバイス」にします。

問題は、移行があるかどうかです。 間違いなくはい! さらに、無条件の遷移(次のコマンドのアドレスはデータの現在の状態に依存しない)と条件付き(次のコマンドのアドレスはデータの状態に依存する)が表示されます。

それらなしで行うことは可能ですか? まさか! 遷移を使用しない場合、メモリなしで組み合わせ回路に戻ります。

その結果、アセンブラーに来ました


そのようなコンピューティングデバイスの神格化は、マイクロコンピューター、シンプルコンピューター、スーパーコンピューターになりました。 それらはすべて、基本的にほぼ同じコマンドセットでアセンブラーに簡単に変換できるコード言語に基づいています。 マイクロコントローラの特定の平均化されたアセンブラを考えてみましょう(ATmeg、PIC、AT90のアセンブラに精通しています)。 彼の移行作業はどのように構築されますか?

遷移は無条件(次のアドレスへの移動、サブプログラムへの移動、終了)であり、条件付き(フラグの状態に応じて)です。

私はすべての責任を宣言します- アセンブラーでの遷移操作なしに行うことは不可能です! アセンブラーのプログラムはどれも目を奪います! ただし、ここで私と主張する人はいません。

まとめ


要約すると何ができますか? マイクロプロセッサレベルでは、移行操作が非常に積極的に使用されます。 それらを使用しない実際のプログラムを作成することはほとんど不可能です(実行できる可能性がありますが、それは超メガ倒錯であり、実際のプログラムではありません!)。 誰もこれについて議論しません。

しかし、なぜ、より高いレベルの言語で-マイクロコントローラのCに集中して-goto演算子が突然支持を失いましたか?..

アルゴリズムについて少し




それでは、トリッキーなアルゴリズムを見てみましょう。 これがどのようなナンセンスなのかはわかりませんが、実装する必要があります。 これがTKであると仮定します。

ここで、A、B、C、D、Eはいくつかの操作であり、関数呼び出しではありません! 多くのローカル変数を使用する可能性があります。 そして、彼らは彼らの状態を変える可能性があります。 つまり、この場合、関数の呼び出しについては説明しません-一部のアクションは詳細になりません。

gotoでは次のようになります。

if (a) { A; goto L3; } L1: if (b) { L2: B; L3: C; goto L1; } else if (!c) { D; goto L2; } E; 


非常に簡潔で読みやすい。 しかし- それ不可能です! gotoなしで試してみましょう:

 char bf1, bf2, bf3; if (a) { A; bf1 = 1; } else bf1 = 0; bf2 = 0; do { do { if (bf3 || b) bf3 = 1; else bf3 = 0; if (bf3 || bf2) B; if (bf3 || bf1 || bf2) { C; bf1 = 0; bf2 = 1; } if (!bf3) { if (!c) { D; bf3 = 1; } else { bf3 = 0; bf2 = 0; } } } while (bf3); } while (bf2); E; 


2番目のリストのロジックから何か理解できますか?..
両方のリストを比較します。



しかし、2番目のリストにはgotoはありません!

このようなアルゴリズムを実装するために、このアルゴリズムも提供されました


if a
A
C

while b or not c
if not b
D
B
C

E



それは美しいようですよね? コピーペーストをしないのはなぜですか、追加のチェックを終了しないのはなぜですか... goto以外のすべてを実行してください!!!

まあ、大丈夫、人生ではそのようなアルゴリズムはほとんど見当たりません。 人生についてのより良い話。

実際のプログラムでgoto



20年以上の経験の中で、私はいくつかのハードウェアプラットフォームと数十個のプログラミング言語を使い 、大きなActiveHDLソフトウェア製品の作成に参加し、商用データベースとオリンピック競技大会で使用される機器をデバッグするための多くの小さなプログラムを作成し、このためのデバイスも作成しましたオリンピックそのもの(正確には、すでにいくつかのオリンピック)。 要するに、私はプログラミングでシャッフルしています。 そして、はい、私は忘れていました-私はKHNUREから名誉卒業証書を卒業しました -つまり、理論的には、私もファックします。

したがって、私のその後の考えや状況...私はそれらに対する道徳的権利を持っていると言ってみましょう。

gotoの暗黙的な使用


Cには、条件付きまたは無条件の、実際には些細なgotoである多くの演算子があります。 これらは、すべての種類のfor(...)、while(...){...}、{...} while(...)ループを実行します。 これは、数値変数switch(...){case ... case ...}の分析です。 これらは、ブレークループとコンティニューループの同じ割り込み/ジャンプステートメントです。 最後に、これらはfunct()関数の呼び出しであり、それらを終了します。

これらのgotoは「合法」と見なされます-goto自体はなぜ違法ですか?

gotoが非難されるもの


彼らは彼が判読不能になり、不十分に最適化されたと非難し、エラーが現れるかもしれません。 これは実用的な短所です。 そして、理論的なものは単に悪いと文盲です、それだけです!

コードの読みやすさと最適化の悪さについては、上記のリストをもう一度見てください。
エラーの可能性に関しては-私は同意しますが、このコードはリストを上から下に読むことに慣れているという事実により、やや複雑に感じられます。 しかし、それだけです! しかし、Cの他の手段は安全であり、コードにエラーを作成できないのでしょうか? はい、少なくとも型変換を行い、ポインターを操作します。 そこを台無しにするには-何もしないで!

ナイフは非常に危険なものだと思いませんか? しかし、キッチンで何らかの理由でそれを使用しています。 そして220ボルト-恐怖、なんて危険なことでしょう! しかし、それを賢く使用すれば、生きることができます。

gotoについても同じことが言えます。 それを賢く使用する必要があります-そして、コードは正しく動作します。

そして、理論的議論について-これは、すみません、好みについての議論です。 ハンガリー記法を使用していますか? 私-いいえ、私は彼女を我慢できない! しかし、私は彼女がこのために悪いと言っているのではありません! 個人的には、変数にはセマンティックロードが必要であると考えています-なぜ作成されるのか。 しかし、他の人がこの命名規則を使用することを禁止しません!

または、a = ++ iを書くことは文盲であると信じる麻酔医がいます。i= i + 1と書く必要があります。 a = i。 そして今、これも禁止するには?

エラー処理


外部デバイスからの入力パケットの処理を行います。

 pack = receive_byte (); switch (pack) { case 'A': for (f = 0; f < 10; ++f) { … if (timeout) goto Leave; … } break; case 'B': … } Leave: … 


パッケージヘッダーを取得しました。 分析済み。 ええ、パッケージ 'A'-つまり、10回何かをする必要があります。 このサイトの稼働時間を制御することを忘れないでください-反対側が凍結している場合はどうなりますか? ええ、それはまだハングしています-タイムアウト条件は機能しました-そして、私たちは外に出ます-ループから、スイッチから。

同じことは、あらゆる種類のフラグで実行できますが、動作が遅くなり、加えてこのような希少なメモリセルを占有します。 それは価値がありますか?

これは単純なケースです。 ただし、receive_byte()は、処理タイムアウトのあるマクロ関数にすることもできます。 そして、そのような鋭い出口もあります。

これは、まさにgotoを積極的に使用する場合です。 これにより、外部デバイス、UART、USBなどで問題が発生したときに立ち往生することがなくなりました。

ネストされたループアウトを終了


以下のプログラムをご覧ください。

 char a, b, c; for (a = 0; a < 10; ++a) { for (b = 0; b < a; ++b) { if (!c) goto Leave; } for (b = 10; b < 15; ++b) { d (); } } Leave: e (); 


何が起こっている-理解しますか? ネストされたループがあります。 何らかの条件が発生した場合、後続の処理はすべて終了します。

フラグ付きのこのコードは異なって見えます:

 char a, b, c, f1; f1 = 1; for (a = 0; a < 10 && f1; ++a) { for (b = 0; b < a && f1; ++b) { if (!c) f1 = 0; } if (f1) { for (b = 10; b < 15; ++b) { d (); } } } e (); 


この場合はどうなりましたか? 各反復で、フラグをチェックします。 さらに確認することを忘れないでください。 繰り返しが多くなく、PCの「無次元」メモリについて話している場合、これらは些細なことです。 そして、プログラムがマイクロコントローラ用に書かれているとき-それはすべて不可欠になります。

ちなみに、この点に関して、一部の言語(Javaで誤解していない場合)では、フォームブレイクLeaveのラベルでループを終了する機会があります。 ちなみに同じgoto!

switch(...){case ...}での処理とまったく同じ例を示すことができます。 異なる構造の着信パケットを処理するときに、これに遭遇することがよくあります。

自動コード生成


自動プログラミングに精通していますか? または他の自動化されたコード生成? レキシカルハンドラーの作成者(大げさなブースト::スピリットを使用せずに)と言います。 これらのプログラムはすべて、「ブラックボックス」として使用できるコードを作成します。内部に何があってもかまいません。 彼が何をするかはあなたにとって重要です。 そして、そこにはgotoが非常に頻繁に使用されています...

1か所で終了


Cでは、次のような記述が必要な場合があります。

 int f (…) { … if (a) { c = 15; return 10; } … if (b) { c = 15; return 10; } …  = 10; return 5; } 


このコードは、次のようにはるかに正確になります。

 int f (…) { … if (a) goto Exit; … if (b) goto Exit; …  = 10; return 5; Exit: c = 15; return 10; } 


アイデアは明確ですか? 時々、終了時に何かをする必要があります。 時々することがたくさんあります。 そしてgotoは非常に役立ちます。 そのような例もあります。
それはすべてをリストしたようです、今あなたは要約することができます...

まとめ



これが私のポイントです! そして彼女は私にとって公平です。 たぶん-あなたのためですが、私はあなたにそれに従うことを強制しません!

そのため、gotoがいくつかの問題を最適かつより良い方法で解決するのに役立つことは明らかです。
また、逆の場合もあります-gotoは多くの問題を引き起こす可能性があります。

UPDたくさんのコメントを読んで、gotoを使用することのプラス面とマイナス面を自分で強調しました。

gotoを使用する利点

gotoを使用することの短所


賛否両論を伝えるのは誰ですか? 彼らが正当化されたら書きます

私はもう一度注意を払います私はgotoにどこにでも行くように促さないでください ! ただし、場合によっては、他のすべての手段よりもはるかに効率的にアルゴリズムを実装できます。

Source: https://habr.com/ru/post/J114270/


All Articles