こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

C言語のGOTO文(組み込み系)

私は組み込みでソフト開発を行っているものです。
(アセンブラでの経験は長いが、Cは短い)
基本的な質問になってしまいますがご了承ください。
C言語で"goto文は使うな、スパゲティプログラムになりやすい。
使うなら多重ループからの脱出のみ" ということを良く聞きます。
本当ににそうでしょうか?・・・

例えば、下記サイトの図6をC言語で書いてみました。
もちろんgotoを使いました。
gotoを使わないで、誰でもわかりやすく書くことなどできるのでしょうか?
よろしくお願いします。


http://techon.nikkeibp.co.jp/article/NEWS/20071119/142670/?ST=lsi&P=2



/* 開始 */

KAISHI:
p74 = High;

if(p70 == High){
register = 1;
}
if(p71 == High){
register = register + 1;
if(register != 1){
goto KAISHI;
}
if(p72 == High){

投稿日時 - 2013-02-17 20:15:57

QNo.7950477

困ってます

質問者が選んだベストアンサー

>初めの10秒間でsw1が押されなかったらエラー処理1を実施して終了。
>無事にsw1が押されると次の10秒がスタートする。
>次の10秒間でsw2が押されなかったら""エラー処理2を行った後エラー処理1を""実施して終了。
>無事にsw2が押されると次の次の10秒がスタートする。
>次の次の10秒間でsw3が押されなかったら""エラー処理3を行った後エラー処理2を行いエラー処理1""を実施して終了。
>です。

それなら、こうすればお望み通りになります。
switch( timerCheck() ) {
case ERR_SYORI3:
____エラー処理3
case ERR_SYORI2:
____エラー処理2
case ERR_SYORI1:
____エラー処理1
____break;
}

・ERR_SYORI3→ERR_SYORI2→ERR_SYORI1
・ERR_SYORI2→ERR_SYORI1
・ERR_SYORI1
ですので、これでOKですか?

投稿日時 - 2013-02-20 00:15:32

お礼

OKです。

投稿日時 - 2013-02-20 21:57:38

ANo.13

このQ&Aは役に立ちましたか?

1人が「このQ&Aが役に立った」と投票しています

回答(16)

ANo.16

なんか熱くなってる方もおられますが

基本は、最近の言語は goto を使う必要が無い
逆を言えば 昔のC言語は goto を使った方が良い部分があった
簡単に言えば、 java や C# にある finally 的な処理

今、java や C# に慣れているのであれば、
昔のC言語でプログラムを組む必要が出てきたなら
finally 的な処理に goto を使うのは全然問題ないでしょう
飛び先のラベルを cFINALLY: と cCATCH: の二つだけ用意して
goto はこの二つのみ許可するのであれば、十分可読性があると言えますし
C# しか知らない人とかでも、理解できるでしょう

投稿日時 - 2013-02-20 09:24:20

ANo.15

私は,およそ半世紀前に起こった goto 文を避けようという運動の目的について,そもそも「わかりやすく書くこと」ではなかっただろうと思います.

ダイクストラさん(故人)のEWD566の邦訳より
「当時やられていたことは,同じ問題について四つの異なるプログラムを作り,それらのうちでどれが一番好きかを何時間も,何日も考える,といったことでありました.」

これは(goto論争の頃を指すのか判然としませんが)今を彷彿とさせます.しかしこの回想の次の展開は工芸から科学を目指しています.

「『わかりやすい』とか『明瞭な』とか『読みやすい』とかいうような,ぼんやりとした,情緒的な用語から,『形式的な正しさの証明』という妥協のない,残忍な概念への移行は...(中略)...一里塚でありました.」

だからといって,いつも証明しろと言われたら私も一目散に逃げるのですが,時折プログラミング入門書などに,連接(;)・選択(if)・反復(while)の三つの構文だけでプログラムを分かりやすく作ることが当時は画期的なことでした,のような解説を見かけるたびに少し悲しくなるのです.

すみません.あまり回答になっていなくて.

参考URL:http://ci.nii.ac.jp/naid/110002753409

投稿日時 - 2013-02-20 01:23:30

ANo.14

zwi

書き忘れました。

>gotoを使った方が一つの関数で済むのでスッキリすのではと主張したかったからです。

それは悪い設計だと思います。
止む負えない場合を除いて関数は単機能が望ましく複数の機能を有するのは悪い設計となります。

理由は単体テストで関数ごとのテスト項目がシンプルになるのでバグが減りやすく、安定した関数を積み重ねることで安定したシステムを構築できると言う点と そもそも目視でのコードの把握もやりやすくなるためバグが入りづらくなると言う点です。

投稿日時 - 2013-02-20 00:31:08

補足

>それは悪い設計だと思います。
>止む負えない場合を除いて関数は単機能が望ましく複数の機能を>有するのは悪い設計となります。
悪い設計ではありません。
このような簡単なフローで関数を2つも用いるのが正直解りにくいです。今回の場合は、むしろGOTOを使って出口を一箇所に集めた方が見やすいです。
素直にGOTO使えば良いのにと思います。
関数化して別けるのは、複雑で行数が長くなりそうな物ほど、
効果があると思います。
元々が簡単なものを関数化して別けるはどうかと思います。
GOTOを使って行き先が不明になるのを防止するため、
関数化するなら理解できます。
行き先が一箇所に集約されているものをあえて関数で別けるのは
おかしいと思います。
正直、個人の考え方によると思いますが・・・。
どんな場合でもGOTOなしで記述できるのでしょうか?
また、GOTOを使った方がスッキリする場合もあると思います。
最後に、いろいろな模範例(組み込み系の)があると助かります。
そういう書籍が無いんですよ。

投稿日時 - 2013-02-20 21:54:32

ANo.12

> do{
> ____if(timer == 0){
> ________エラー処理3
> ERR_SYORI2:
> ________エラー処理2
> ERR_SYORI1:
> ________エラー処理1
> ________break;
> ____}
> }while(sw3 == off);

・ループは先頭から開始する
・ループ中にifがあったら、そのループ中で発生する現象を判定し、処理する
・if文で、then部はifの条件が真のとき、else部には偽のときだけ実行される
と考えるのが自然ではないでしょうか?

sw3と関係無いエラー処理1,2が、どうしてこんな箇所にあるのか?
今回はたまたまtimer==0という条件が一致していますが、それは、プログラム全体を調べて、そこからしか飛んでこないのが明らかになったから言えることで、ここだけ見たらtimer==0とエラー処理1,2との関係もわかりません。

「gotoを使うのがわかりやすい」例を作ったつもりが、スパゲッティの見本になっています。

最初のフォローチャートも、追加されたプログラムも、まずやるべきなのは、そのままgoto文を使うことではなく、設計を見直すこと、ではないでしょうか?


構造化プログラミング、オブジェクト指向プログラミング、関数型プログラミングといったものは、間違ったプログラムに苦労した研究者達が、どうしたら正しいプログラムを楽に書けるか、考えた結果生れてきた手法です。一度、これらのプログラミング方法について、概論だけでも読んでみてはいかがでしょうか?


以下、gotoとは関係無い話
> return (register == 1) ;
> はどういう意味でしょうか?

関数からのreturnには戻り値を指定できる、というのはよろしいですね?
C言語では、==や<,>といった比較は、「比較演算子による演算」になります。
== 演算子は、「左と右が等しかったら1,異っていたら0」という「計算」をしたものが、値になります。
なので
a=(b<c) ; /* b<cならa=1, b>=cなら a=0 */
といった使い方もできます。

> 「音を鳴らしてはならない」なら0,「音を鳴らす」なら1を返す checkButton() 関数
を作りました。「音を鳴らす」のは「register==1」の時なので、register==1なら1、そうでないなら0を返さなければなりません。
そこで return (register == 1) です。

投稿日時 - 2013-02-19 08:44:54

ANo.11

zwi

>説明:
>初めの10秒間でsw1が押されなかったらエラー処理1を実施して終了。
>無事にsw1が押されると次の10秒がスタートする。
>次の10秒間でsw2が押されなかったらエラー処理2を実施して終了。
>無事にsw2が押されると次の次の10秒がスタートする。
>次の次の10秒間でsw3が押されなかったらエラー処理3を実施して終了。

No.4回答者さんの方法なら別に問題なく書けますよ。

------------- ここから
/*開始*/

int timer //型宣言 timerは1秒割込みで0になるまで1秒置きに-1されるものとする

enum {
ERR_NASHI,
ERR_SYORI1,
ERR_SYORI2,
ERR_SYORI2,
};

switch( timerCheck() ) {
case ERR_SYORI1:
____エラー処理1
____break;
case ERR_SYORI2:
____エラー処理2
____break;
case ERR_SYORI3:
____エラー処理3
____break;
}

int timerCheck()
{
____timer = 10; // 10秒をセット
____while( sw1 == off ) {
________if(timer == 0) return ERR_SYORI1;
____}
____timer = 10; // 10秒を再セット
____while( sw2 == off ) {
________if(timer == 0) return ERR_SYORI2;
____}
____timer = 10; // 10秒を再々セット
____while( sw3 == off ) {
________if(timer == 0) return ERR_SYORI3;
____}
____return ERR_NASHI;
}
------------- ここまで

とにかく、いろんな手法を心得た上でgotoが一番簡潔だと言う結論なら分かるのですが、gotoまず有りきに見えるのが気になるところです。
私も場合によっては使いますよ。


この処理はどうかと思います。
>do{
>____if(timer == 0){
>________エラー処理3
>ERR_SYORI2:
>________エラー処理2
>ERR_SYORI1:
>________エラー処理1
>________break;
>____}
>}while(sw3 == off)

投稿日時 - 2013-02-18 23:12:49

補足

例題の説明ですが、私の説明が悪かったです。すいません。
正しくは、
初めの10秒間でsw1が押されなかったらエラー処理1を実施して終了。
無事にsw1が押されると次の10秒がスタートする。
次の10秒間でsw2が押されなかったら""エラー処理2を行った後エラー処理1を""実施して終了。
無事にsw2が押されると次の次の10秒がスタートする。
次の次の10秒間でsw3が押されなかったら""エラー処理3を行った後エラー処理2を行いエラー処理1""を実施して終了。
です。

gotoを使った方が一つの関数で済むのでスッキリすのではと
主張したかったからです。
とある書籍では「スパゲティになるのでgotoは使わないように」と目の敵のように書かれており、納得がいかなかった次第です。
アセンブラでは、ブランチ命令のようにラベルに飛ばすことは、
当たり前のように行っていたのにC言語になると「goto使うな」です。どの書籍も説明不足なんです。
今回のように、gotoで書いた例文をgotoのない文に変更して、
比較するなどの説明をしてほしいのものです。

投稿日時 - 2013-02-19 22:55:23

ANo.10

基本は会社の方針に従え、だと思います。

例えば、

while(1)
{
if( 条件 ) break;
処理
if( 条件 ) break;
処理
if( 条件 ) break;
処理
処理
処理
break;
}
処理


と書く人がいました。先頭の while や最後の break の意味、やってる事を要約すると
goto を使うと可読性が悪くなるという理由で goto の処理をを可読性悪く書いてるだけという
意味不明な状態です

C言語は、いわば昔の言語です。 最近の C# や JAVA 等 goto を使うよりもベストな方法が
別に用意されている言語とは違うのですから
goto にこだわらず、臨機応変に作れる事が理想でしょう

投稿日時 - 2013-02-18 16:02:08

補足

>goto を使うと可読性が悪くなるという理由で
>goto の処理を可読性悪く書いてるだけという
>意味不明な状態です

確かにそうですね。

投稿日時 - 2013-02-18 21:52:27

付け足しですが「GO TO論争」(bit誌 1975年5月)という記事を読まれたことがなければ、一読することをお勧めします。このような議論は過去にも相当に行われています(主に米CACM誌で)。これらの雑誌は大きな図書館に保存されています。

http://memo.ptie.org/bit/1975

内容は平行線です。goto文の支持派と反対派(形式手法よりな人々)は話がかみあわないので。それはそれで面白いですが。

IPAルールなど既存の成果や整理された経験則を参考にコーディング規約を考える方が有意義だと私は思います。

http://sec.ipa.go.jp/publish/tn06-004.html

投稿日時 - 2013-02-18 12:15:16

ANo.8

今回の例であれば、do文を使うのが妥当だと思います。
使用するマイコンμPD78F0547Dは決して高速ではなく、メモリサイズも小さいので、遅く大きくなるとわかっている、またはその可能性が高いコードを書くのは時期尚早な不最適化にもつながります。
その意味で、while文や関数の呼び出しは避けるべきです。ましてや、gotoを回避するためだけの新たな変数を導入するのは論外です。
(1KバイトしかないRAMを、本来不要な関数呼び出しのためのスタックや、変数で浪費すべきではありません)

> C言語で"goto文は使うな、スパゲティプログラムになりやすい。

必ずしもgoto文を使ったからスパゲッティになりやすいというわけではありません。
gotoを使わなくてもスパゲッティになるときはなりますし、gotoを使ってもならないときはなりません。
結局はプログラマ次第なので、本当に使ってはならないのは、特定の構文ではなく下手糞なプログラマの方です。

> 使うなら多重ループからの脱出のみ" ということを良く聞きます。
> 本当ににそうでしょうか?・・・

私の場合、終了処理やエラー処理にも普通にgoto文を使います。
たとえば、割り込みハンドラでは特別な出口処理が必要になることが多いと思います。
その場合、出口を一か所に集中させるにはgoto文を使う方がむしろ可読性が高いはずです。

ただ、今回のような単なるループを作るためにgoto文を使うことは、よほど特別な事情がない限りありません。
異常時にリトライを行う場合などは、結果的にループであってもgoto文を使うことはあります(エラー処理なので)。

職場のコーディング規約等でgoto文が禁止されているならそれに従わざるを得ませんが、質問者さんの開発経験はある程度長いようなので、お仕着せのコーディング規約に盲従するだけではなく、定期的に見直しをかけて適切な内容に改善していくべき立場ではないでしょうか?

投稿日時 - 2013-02-18 11:10:09

補足

>出口を一か所に集中させるにはgoto文を使う方がむしろ可読性>が高いはずです。

私も同じです。
出口を一か所に集中させるにはgoto文を使います。
今回は例題になるフローチャートを無理やり探してきたんです。
すいませんでした。

投稿日時 - 2013-02-18 21:57:04

> gotoを使わないで、誰でもわかりやすく書くことなどできるのでしょうか?

goto文を使わないことは、大半のプログラマが可能でしょう。

しかしプログラムの正しさの証明や、それが容易なプログラムの作成は、そのための訓練を積まないと不可能でしょう。

1970年頃から始まった goto 文を使うなと教えは、証明を念頭においた(初学者に対する)プログラミング教育の準備段階です。状況に応じて goto 文をどう避ければよいかの判断は、その先にある技術を習得してからでないと難しいと思います。誰でも論理や集合をプログラミングに応用できるわけではありません。とりあえず何も考えず goto 文を排除するというのは一つの手でしょう。

投稿日時 - 2013-02-18 00:55:13

ANo.6

私はジジイですが、C/C++でgotoを使った経験もないですし、使おうと思ったこともありません。
フローチャートもずっと昔から書かなくなりました。
アセンブラは流石にブランチ命令は必須ですが…
歳が歳だけに、コーディングステップ数はアセンブラが一番多いです。
16ビット、32ビットやって最後に8ビットをやりました。

それでも1980年ころには構造化プログラミングは取り入れていて、フローチャートやgotoから縁が切れていました。
いや、Fortranの時にはgotoが復活しましたか。
C/C++ですとやはり細かく関数化したり、自分で自分を呼び出す再帰関数になりやすいです。
Lispをやっていた時には、doさえ使わず、繰り返しも全て再帰関数でやっていました。

勿論、オブジェクト指向がこの世に出た時には、これぞソフトウェア界の革命と呼べる技術だと思い、直ぐに取り入れました。

私は「誰にでも分かる」という言葉は無いと思っています。
プロに分かれば良いのです。
エラーが出難い設計、
開発効率のよい設計、
部品の再利用がしやすい設計、
などなどをとことん考え、勉強し、思想やノウハウを身につけた人にだけ分かれば良いのです。
しかも、何時までも更に良い方法がないかと、満足しない人ですね。プロとは。

オブジェクト指向をやっていると、多くの人が使い方や手順を覚えるだけで、意味を理解しようとする人が少いです。
これではオブジェクト指向は使わない方がマシなプログラムになるでしょう。
それでも私はオブジェクト指向です。
分からない人は、理解しようとしない人は置いてきぼりにされても仕方がないと考えます。
素人が足を引っ張ってレベルを下げることは、私には我慢出来ません。

投稿日時 - 2013-02-18 00:01:39

 No.4回答者さんが正解です。構造化プログラミングです。再帰呼び出しも出来るように(自分から自分を呼び出す。)なります。
 ただメモリーマップドI/Oと思われますので切り替えてからある程度のディレイ時間が必要かも知れません。
 読み込みポート指定出力してからの時間をWRITE_DELAY_TIME,
 読み込んで次のポートを読み出す為の時間READ_DELAY_TIMEが必要かも知れませんので
 万が一に備えて#defineで宣言して変更し易くしておくのが良いかと思われます。
 Sleep()はスレッド停止時間が使用出来ればそのまま、使えないならForNextLoopでSleep()を新規に作成します。
 組み込みのハードウェアのタイミングについては質問者さんが一番ご存知だと思われますが。

#define WRITE_DELAY_TIME 0
#define READ_DELAY_TIME 0

int checkButton(){
int register=0;
p74 = High;
Sleep(WRITE_DELAY_TIME);
if(p70 == High){register ++;}
Sleep(READ_DELAY_TIME);
if(p71 == High){ register ++;}
Sleep(READ_DELAY_TIME);
if(p72 == High){ register ++;}
Sleep(READ_DELAY_TIME);
...
return (register == 1) ;
}

投稿日時 - 2013-02-17 23:05:59

補足

今回の場合は、DO-WHILE文で代替ができそうです。
では、下記のような場合はどうでしょう。
N0.8さんが言われる「出口を一か所に集中させるにはgoto文を使う」です。
果たして、goto文を使わないでできるのでしょうか。

説明:
初めの10秒間でsw1が押されなかったらエラー処理1を実施して終了。
無事にsw1が押されると次の10秒がスタートする。
次の10秒間でsw2が押されなかったらエラー処理2を実施して終了。
無事にsw2が押されると次の次の10秒がスタートする。
次の次の10秒間でsw3が押されなかったらエラー処理3を実施して終了。


/*開始*/

int timer //型宣言 timerは1秒割込みで0になるまで1秒置きに-1されるものとする

timer = 10; // 10秒をセット
do{
____if(timer == 0){goto ERR_SYORI1;}
}while(sw1 == off)

timer = 10; // 10秒を再セット
do{
____if(timer == 0){goto ERR_SYORI2;}
}while(sw2 == off)


timer = 10; // 10秒を再セット
do{
____if(timer == 0){
________エラー処理3
ERR_SYORI2:
________エラー処理2
ERR_SYORI1:
________エラー処理1
________break;
____}
}while(sw3 == off)

/*終了*/

投稿日時 - 2013-02-18 22:41:55

ANo.4

私なら、こんな感じにすると思います

while(継続条件) {
if( checkButton() ) {
playSound();
}
}

そして、その流れ図のようなチェックをして、「音を鳴らしてはならない」なら0,「音を鳴らす」なら1を返す checkButton() 関数を作ります。
コードは、あなたが書いたのと似たのになりますが、
int checkButton(){
int register=0;
p74 = High;
if(p70 == High){register ++;}
if(p71 == High){ register ++;}
if(p72 == High){ register ++;}
...
return (register == 1) ;
}
と、gotoは使いませんし、KAISIへ戻ったりもしません。


さて、このプログラム、「わかりづらい」でしょうか?


gotoが害、というのは、構造化プログラミングという考え方から来たものです。
http://ja.wikipedia.org/wiki/%E6%A7%8B%E9%80%A0%E5%8C%96%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0
現在主流のオブジェクト指向は、この構造化を発展させたものです。
現在主流の言語の多くは、goto文がありません。「ループからの脱出」等の特定の使い方に限定されたジャンプ命令(continue,break等)があるだけです。

では、どうするのか?
最初からgotoを使わない設計をします。

その記事をざっと斜め読みしましたが、初級者がアセンブリでプログラムしているようです。
Cでのif+gotoにあたる条件付分岐命令を使うことを念頭にフローチャートを作っているように思われます。
命令の少ないアセンブリでは、それが正しいやりかたかもしれません。
それをそのままCへ持ってこようとすれば、どうしてもgoto文を使いたくなります。

ですが、最初から、例えば構造化プログラミングを念頭に設計すれば、フローチャート自体が別なものになるでしょう。
goto文を使うことも無いはずです。

/* フローチャート自体も、最近のプログラミング設計には向かないと言われています */

投稿日時 - 2013-02-17 22:05:48

補足

回答で書かれた文において、
最後の文の

return (register == 1) ;

はどういう意味でしょうか?
教えてください。
宜しくお願いします。

投稿日時 - 2013-02-18 21:46:47

ANo.3

zwi

私もdo~while()で書いちゃいますね。

やりたいことからすると、無駄が有るけどこんなかんじで。
____do {
________doremi = 0;
________register = 0;
________p74 = High;
________if(p70 == High) {
____________register++;
____________doremi = 1;
________}
________if(p71 == High) {
____________register++;
____________doremi = 2;
________}
________if(p72 == High) {
____________register++;
____________doremi = 3;
________}
________if(p73 == High) {
____________register++;
____________doremi = 4;
________}
________
________・
________
________・
________
________
____} while( register!=1 );

投稿日時 - 2013-02-17 21:14:46

ANo.2

私ならこう書きます。
まあ、do-while文も結構敬遠される書き方で、
do-whileにせざるを得ない設計は
他人のことを考えない悪い設計と私の職場なら言われかねません。

do{
 p74 = High;

 if(p70 == High){
  register = 1;
 }
 if(p71 != High){
  break;
 }
 register = register + 1;
}while(register != 1);

if(p72 == High){
 ・
 ・

投稿日時 - 2013-02-17 20:56:45

組み込み系という事で、企業などでPGをされている方だと仮定しますが、余所の人間がどうこう言おうと、職場の宗教に合わせるのが良いと思います。
(個人であれば、明日の自分の事を考えて使用頻度を見極めれば良いかと)

GOTOを使わないというのは、単に他人がそのソースをメンテするときに処理を追いにくくなるからであって、要はその他人がGOTOを追い切れる土壌にあるならばそれなりに使って構わないと思います。

ちなみに自分の職場ではGOTOの使用は『例え何があっても』御法度といった宗教で動いていますが(GOTOを使わなければ書けないアルゴリズムはそれ自体が間違っているとのこと)、自分でしかメンテしないモジュールには適度にGOTOを使っています。神の領域に逝ってしまった諸先輩の技量に合わせるために残業はしたくありませんので……。

GOTOを使えばステップ数もそれなりに減るのに、使わない為にIF文を8重くらいにネストしたコードを弄ったことがありますが、そこまで来ると別の弊害を感じました。そのくせやたらめったらグローバルに変数置いてたりして、一体どうなってるのかと。

要は、何事も適度がいいって事だと思います。

投稿日時 - 2013-02-17 20:46:54

あなたにオススメの質問