• ベストアンサー

関数引数に対する制限値チェック方法

引数に対する制限値チェック方法  プログラミング(組み込み系C言語)関数作成時にいつも私が迷ところなのですが、  関数の引数に対する制限値(範囲外)チェックどうするか、次の(1)、(2)で悩んでます。。   【1】関数内で制限値チェックで行い、制限値外であれば戻り値でエラーコードを返す。   【2】関数呼び出し時に、引数となる値(変数)をチェックし、制限値内であることを確認してから、関数を呼び出す。  上記の【1】、【2】の方法どちらがよいでしょうか?  状況にもよるとは思うのですが、その場合はどういった状況時に(1)||(2)がよいのか教えてください。  (【3】もあればお願いします。)  --【1】がお勧めの場合の質問  (1)本関数での"結果"を返したいときどうすればよいかアドバイスください。     戻り値("結果")と、エラーコードを兼用するのはなんかイヤです。。     エラーコード付き関数は、全て同じ戻り値(1:OK時、-1:NG時 みたいに)     としてまとめたいからです。  (2)極端にほとんどの関数の戻り値を、OK/NG とす。これってどうですか?、 ///////////////////////  【1】、【2】の利点、欠点を僕なりに考えてみました。  ##【1】の利点/欠点  利点:   ・本関数呼び出し時に、毎回制限チェックをしなくてよくなる。    (汎用的に様々な場面で、使用するのであればこれは良い利点だと思います。)  欠点:   ・戻り値のとして、エラーコードを返さなくてはならないため、本関数での結果を返したい場合、    以下方法をとらないといけない。      1、引数をポインタとして、その引数で値を返す。      2、戻り値として、エラーコードと兼用して返す。        (例:エラー時の戻り値 = 0、正常にの戻り値 = 1~ 255)  ##【2】の利点/欠点  利点:   ・エラーコードを返す必要がなくなるため、戻り値が有効利用できる。  欠点:   ・関す呼び出し毎に、制限チェックが発生し、制限チェック忘れが発生する。    (汎用的に使うにのであればなお・・・)

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

  • ベストアンサー
  • WizTaka
  • ベストアンサー率53% (7/13)
回答No.3

私も何度も考えたことがある疑問ですね. C++ とか C# でも,こういう場合 "例外処理" という方法を使ってしっかり対処するくらいなので, 間違いなく【1】が有利だと思います. 例外処理を使えないとなると,"結果をもらうのにポインタ引数を使う" ということがしばしば出て きてしまいますよね. 分かっていても,やはり見苦しいものがあります. エラーか否かを 0 か 1 (C++ とかで言う bool) で返しつつポインタにした引数に値を入れようと すると,0 (エラー) を返すときに引数に何を入れて返してよいか分からなくなるからです. 確かに,"どうせエラーだから何でもイイ" のですが,"何でもイイ" というコードを書くこと自体 ナンセンスで,スマートではないですよね. それに,基本的には "結果" を "メソッドに対する戻り値"として期待しているのに,わざわざ 引数として与えなければならないところに違和感を感じます. (当然,メソッドに "結果" をふたつ同時に求めない場合ですが) それ以外にも,プログラムのリーダビリティを損なうというか,読んでいて気分が悪くなりますよね. 例えば,以下の Type A と Type B のどっちが読みやすいかということについてですが,きっと Type A の方を好む方が多数だと思います. //------------------------------------------------------------------------------ ※ input には何が入っているか分からない ※ Type B の FunctionFoo は,エラーのときは 0,そうでないときは 1 を返す //************************************** // Type A //************************************** if( この条件式を満たさないための値 != input ) {   int output = FunctionFoo(input);   /* output を使用するその後の処理 */ } //************************************** // Type B //************************************** int output; if( FunctionFoo(&output, input) ) {   /* output を使用するその後の処理 */ } //------------------------------------------------------------------------------ 結論から言うと【1】になるのですが,やはり理想としては Type A のようなリーダビリティも 求めたくなるのが現状です. (つまり,例外処理を使えないことに苦言を呈したい)

1990san
質問者

お礼

やっぱり引数で結果をうけとるってなんかスッキリしないですよね~ Type Aでも関数呼び出すたんびに制限チェックしていたら、 なんかリーダビリティも低くんっているような気がします。 これだって方法ないんでしょうか??

その他の回答 (4)

  • rinkun
  • ベストアンサー率44% (706/1571)
回答No.5

ANo.4補足 > >【3】関数内で制限値チェックで行い、制限値外であれば呼び出し元には戻らない。 > これは制限値外ならばプログラムを停止するということなのでしょうか? そう。プログラムに間違いがあるとどんな被害が発生するか分からないので可及的速やかに停止させるべきという考えです。 # プログラムに間違いがなければ制限値外にはならないことが前提です > また、結論としては、【1】、【2】どちらも必要と考えてよいんでしょうか? 基本的に【2】です。ただそれだけだと間違いの発見ができないのでデバッグ用にassertを入れましょうということです。 assertだとリリース時にはチェックしません(参考URL参照)。当然、プログラムはassertがチェックしなくても問題のないコードでなければいけません。関数内で制限値チェックをしているから呼び出し側ではチェックしないというようなコードは許されません。 # もちろんロジック的に制限値を外れる恐れはないからチェックしないというのはOKです

参考URL:
http://www.linux.or.jp/JM/html/LDP_man-pages/man3/assert.3.html
  • rinkun
  • ベストアンサー率44% (706/1571)
回答No.4

一般的に安全性を考えて【1】を行う場合が多いようですが、プログラムを複雑化させてバグを増やす原因になります。 下記URLの「オブジェクト指向入門」に書かれている「契約による設計」の概念に沿えば、【2】の方がお勧めでしょうね。 # http://www.seshop.com/detail.asp?pid=7595 ただ上記書籍でも関数の入り口でのチェックは行っているので、正確には次の方法が良いと思います。 【3】関数内で制限値チェックで行い、制限値外であれば呼び出し元には戻らない。 具体的にはassertを使って制限値チェックを行うか、同様の自作関数(マクロ)を用意してデバッグに活用する。 # 自作関数ならデバッグ時にはこの関数内にブレークポイントを置けば確実に捕捉できスタックトレースなども取れる この方法は、【1】のインタフェースが複雑化する欠点と【2】の制限チェックを忘れる欠点の両方をカバーすることができます。 なお、呼び出し側の制限チェックを省くことはできません。制限を満たさない呼び出しはあってはいけないからです。

1990san
質問者

補足

>【3】関数内で制限値チェックで行い、制限値外であれば呼び出し元には戻らない。 これは制限値外ならばプログラムを停止するということなのでしょうか? また、結論としては、【1】、【2】どちらも必要と考えてよいんでしょうか?

  • hidebun
  • ベストアンサー率50% (92/181)
回答No.2

あらゆる場面において【1】にするべきです。 制限値チェック云々より、「責任範囲の明確化」です。 【1】のルールでその関数の責任が明確化されます。 使う人によって、毒になる関数があったら、 障害が起きたときに追跡が大変です。 障害の原因を探すときには、コードのどこからどこまでが、 シロか調査範囲を切り分けますが、【2】のポリシーで作成した 関数の使用箇所が、ありとあらゆる場所にちりばめられていたら、 シロと言える場所がなくなり、ソースコードを全探索する羽目になります。 「A君の作った関数、変な値入れられたらどうなるの?」 って突っ込まれたら、どうします? 「僕の作った関数は入力チェックをしていますから、エラーを返します」 と、言えたら良いですが、 「使う人がちゃんとチェックしていれば、問題ないですが、範囲外の値を入れられた 場合の動作はわかりません。」 なんて言ったら、 「それじゃ、使用箇所全体が信用できんじゃないか。」 となってしまいます。 さらに、チェック忘れ以外に、チェックミスも発生することも忘れないで下さい。 断然チェックミスのほうがたちが悪いです。 まあ、【2】でやってみて、痛い目を見るのも良い経験でしょうが、 周囲の信頼を得たいなら【1】のポリシーでいくのが固いと思いますよ。 >戻り値("結果")と、エラーコードを兼用するのはなんかイヤです。。 >極端にほとんどの関数の戻り値を、OK/NG とす。これってどうですか?、 頭の中まで、論理回路にしなくても良いですよ。 戻り値=成否、エラーコードを返すと固定的に考えるからイヤになるのでは? エラーコードじゃなくて、結果だと思えば、 0:異常なし 1:故障1 2:故障2 3:故障3 と思えば、自然じゃない?? if( result == 0 ) {  // 異常なし } else {  // 故障 }

1990san
質問者

お礼

【1】で責任の明確ですか。。そのとおりですね! 【1】がよい気がしてきました! 痛い目あうのはいやです・・

noname#50176
noname#50176
回答No.1

下記のようにしてはだめですか? 質問者さんの「1」の簡素化で別々の関数にします。 あまり気に入らないかもしれませんが… <サンプル> #define FUNC(n) (n>=0)?func(n):err() // 上行の「n>=0」の部分に範囲内の条件 int func(int N) { printf("正常範囲です\n"); // エラー発生時は、return エラー値>0 return 0; } int err(void) { printf("範囲外です\n"); return -1; } int main() { FUNC(1); // 範囲内 FUNC(-2); // 範囲外 return 0; }

1990san
質問者

お礼

なるほど、こんな工夫もできるのですね! 参考になりました。ありがとうございます。

関連するQ&A

  • 関数の引数に 値を入れることを 渡す その関数が結

    関数の引数に 値を入れることを 渡す その関数が結果を出すことを 返す とか 返り値戻り値 という この認識であっていますか?

  • 戻り値と引数

    戻り値と引数の概念がよくわかりません。 質問内容は2つ (1)「戻り値は値を関数元に返す」 とありますが 返すとどうなるのですか? また返さないとどうなるのかを教えて下さい。 (2)引数に関しては眺めていればこの引数が値を渡してるというのは なんとなくわかるのですが、実際のプログラムを組んで関数を作成するときに何を引数にすればいいのかさっぱりです。それを教えて下さい。

  • GDBでCプログラムの関数の引数を変更したい

    関数の引数の値を書き換えたいです。 DGBのx/iコマンドでアッセンブラをみて レジスタに格納された引数の値を書き換えようと考えています。 このコマンドで書き換えたい関数の$ebpを見て書き換える予定です。 引数を書き換えたい関数の戻り値が構造体であるかないかで、$ebpからのアドレスの位置が変更されてしまいます。 他に格納されている関数の引数のアドレスが1つに固定できる方法はないでしょうか? できないとしたら、条件で関数の戻り値が構造体かどうかの判断はどこでつければよいでしょうか?

  • 関数を引数とする方法?

    いつもお世話になっています。 MFCでプログラムをしています。 今、任意の関数(Func1)を 積分する関数(Func2)を作っています。 現在は、被積分関数の数だけ、 積分関数(Func2)を書いているのですが、 非効率的なので、なるべく汎用性を持たせたいと 考えています。 参考書(新C言語入門シニア編)の該当個所で、 クラスでない通常の関数を引数とする場合は、 うまくいったのですが、 クラスのメンバ関数を引数とした場合、 どうしてもコンパイルエラーが 発生してしまいます。 関数Func、I及びエラーメッセージは大凡次のとおりです。今のところ、引数とする関数(Func1)の引数は、 同一個数としています。 <被積分関数の例> double ClassA::Func1 (double a){ return a * 10; } <積分関数> double ClassA::Func2 (double (*f)(double), double a, double b){ return b * f(a); } void classA::Integration() { ... Func2(Func1,a,b); ... } <エラーメッセージ> classA::Integrationの呼び出し箇所で、 「1番目の引数を double(double)からdouble(__cdecl)(double)に 変換できません」 と出ます。 double(double)の部分は合っているようなのですが、 (__cdecl)の部分が違うということまでは 分かりました。 メンバ関数であることが原因のようなので、 Func2での引数宣言を double ClassA::Func2 (double (ClassA::*f)(double), double a, double b){ return b * f(a); } に変えてみたところ、 引数受け渡しのところはクリアするのですが、 Func2(Func1,a,b); の呼び出し時に、Func2が関数ではないという エラーがでます。 アドバイス又は参考URL等を 教えていただければ助かります。 よろしくお願いします。

  • シェルの引数の入力チェックをしたい・・

    シェルは初心者のため、ご教授ください。 3つの引数を受け取り、値のチェックを行いたいのですが書き方がよくわかりません・・。 やりたいチェックは、 第1引数は 数字じゃなければエラー 第2引数は 文字列が "YES" か "NO"でなければエラー 第3引数は 日付のフォーマットチェック(YYYY/MM/DD以外はエラー) です。よろしくお願いします。

  • 関数の引数に引数を持つ関数を入れたい場合はどうすればよいのでしょうか。

    関数の引数に引数を持つ関数を入れたい場合はどうすればよいのでしょうか。 function hoge(m, l, p, h) {} (中身は省略しています。) この関数の引数「h」にhtmlを代入しようとしています。 そのhtmlには、WindowOpen()があり、そのまま代入すると「")"がありません」 エラーが発生するのですが、これは無茶なことなのでしょうか。 なんとか解決方法があれば教えていただけませんでしょうか。 「"」の時のように「\"」ではだめだったので行き詰っています。

  • ラジオボタンの値を取得する引数付き関数を作りたい

    フォームの入力内容をチェックするJAVASCRIPTの関数を作成していますが、ラジオボタンのValueを取得することができません。 引数指定の関数を作成して汎用的に使用したいと考えているのですが、引数を渡すと値を返してくれません。 引数の渡し方がおかしいのでしょうか? function getRadioValue(str){ // 値を取得する関数 var check, num, value="none"; num=document.form1.str.length; for (i=0;i<num;i++) { check=document.form1.str[i].checked; if (check) value=document.form1.str[i].value; } return value; } function check(){ // 入力内容をチェックする if (getRadioValue("タイプ") == "A"){ hogehoge } : : }

  • 引き数がポインタでない関数の戻り値はなぜint型なのか?

    最近、疑問に思っていることがあって質問したいんですが、一般的にc言語 で使われる関数っていうのは、引き数としてポインタをとる関数以外は全て 戻り値はint型なのですがこれはなぜでしょう。私自身の結論としては、も し関数の戻り値がchar型だとしたらchar型で表せる範囲の値は全てasciiコー ドとして使われているので、もしエラーがあって戻り値としてEOF(-1)を返す 際に1byteでは同じビット配列になる255が既に使われていてEOFとして-1を定 義できないために、-1と255が同じビット配列ではない1byte以上のデータ型 つまりint型(たぶん、int型がコンピュータが一番効率良く処理できるデータ型なのでたぶんint型を使ったと思いますが)を使ったのではないかと言う 結論に達したのですがこれは正しいのでしょうか。どなたか御教授お願い致 します。

  • GCCで関数の引数が渡らない

    gcc Ver2.9 でSH-2の開発をしています。 通常に関数を作成し、引数を渡しているのですが、引数が渡らないという現象が起きています。 現象は、 1.引数はポインタではなく値渡しである 2.引数の値が0の時だけ正しく渡らない。値が0以外の時は正常にわたる 3.引数の型は一致している 4.引数は複数あるが、後半のいくつかがだめ(何個とまでは詳しく調べていません) 5.ある特定の関数の特定の呼び出しのみがだめで全てだめというわけではない 6.コンパイルオプションに -m2 をつけるとだめだが、-m1 オプションだと問題ない 7.最適化オプションをなくしても同じだった といった状況です。 上記5からある特定の記述方法とか順序になるとだめになるのではないかといろいろ試してみたのですが見つけられません。6から記述方法に誤りがあるとも考えにくい状況です。コンパイラのバグといって片付けていいものなのかどうかです。どなたか同じような経験をされた方はいらっしゃいませんか。また関数呼び出しの場合、コンパイラがどうやって引数を渡すかご教授願えませんか。

  • 引数について質問

    私プログラミング初心者ですので、できるだけ優しい解説をしていただければ幸いです! 引数について、以下のような解説がありました。 「引数には仮引数と実引数の2種類が存在する。仮引数は、関数を定義する際に変数で指定する引数である。また、実引数は、プログラムの実行時に関数に引き渡される値となる引数である。つまり、関数の実行時には、実引数の値が仮引数に代入されることになる。」 質問:1「関数を定義する際に変数で指定する引数である。」という記述の中で「関数を定義」とありますが、実際のソースコードにおいて何に対応するかわかりません。簡潔なソースコードを交えて解説していただければ幸いです。 質問2:「関数を定義」に限らず、プログラミングにおいて「定義」という言葉をよく見ますが、これは本質的にどういう意味をもっているのでしょうか?具体的なソースコースコードを交えて解説してくださると幸いです。 もしかして、その定義とは例えば「public static void main(String arg[]){」のような「メソッド宣言」のことですか? 質問3:「関数の実行時には、実引数の値が仮引数に代入されることになる。」と書いてありますが、 これはどういうことですか、僕が実際にソースコードで記述してみるので、その考えが正しいか判定してください package 第4章; public class A { public static void main(String arg[]){ double x; x=Math.sqrt(2.0); System.out.println("2.0の平方根は"+x); } } 僕の考え:String arg[]が仮引数で、実引数2.0がString arg[]に代入されるってことでしょうか? 「定義」といえば、上記のソースコードでは、public static void main(String arg[]){ 以外見当たらないので、、 僕の考え2:Mathクラスは、標準クラス(javaが最初から備えているクラス)だから、プログラマが「関数を定義」しなくても予め関数が定義されているから、関数を定義する必要がない、ということでしょうか?

    • ベストアンサー
    • Java

専門家に質問してみよう