

初心者な質問で申し訳ありません。

Cで自作関数を作成した場合、  
戻り値で何を返すか迷ってしまいます。

戻り値の型をint型とした場合、  
エラー時は-1を返すべきなのか0を返すべきなのか

また、戻り値の型をchar *等ポインタ型にした場合、  
エラー時は空のポインタを返せばよいのか

上記以外にもありますが、  
皆さんのアドバイス、ご意見をお願いします。


CC BY-SA 3.0
2015-03-24T06:05:23.523
8305
2018-08-12T16:52:38.113
8984
question
c
Cの自作関数における戻り値について


> 戻り値の型をint型とした場合、  
>  エラー時は-1を返すべきなのか0を返すべきなのか

いずれにせよ、「一貫性を持つ」というのが重要です。

他のライブラリと一緒に使う場合、ライブラリにおいて0をエラーとして返す関数で構成されている場合、自作関数もそれに合わせる(一貫性のため)のが混乱を減らせると思います。

ただ、複数のライブラリと組み合わせる場合、あるライブラリでは0エラーで、他のライブラリで-1エラーとなっている場合、一貫性は持たせられません。

この場合は、別途、エラーがわかりやすい書き方をしておく方がいいでしょう。

[code]

    if (val < 10) {
        return 0;
    }
    
[/code]

というコードでは、`return 0`が正常終了なのか、異常終了なのかわかりません。

なので、

[code]

    if (val < 10) {
        return NO_ERROR;
    }
    
[/code]

なり

[code]

    if (val < 10) {
        return ERR_OUT_OF_RANGE;
    }
    
[/code]

というような書き方にしておくと、後々のソースリーディングが楽になります。


CC BY-SA 3.0
2015-03-24T06:28:02.987
8307
2015-03-24T06:28:02.987
4840
8305
answer


一般に戻り値のある関数の戻り値は「値」か「意味」になります。  
int としてしまうと戻り値は「整数」に過ぎません。  
「意味」を戻す場合の方法としては、例えば

[code]

    typedef enum CALL_SATUS // 実行結果
    {
      CALL_SATUS_OK  , // 成功
      CALL_SATUS_INVALID_PARAM ,// 引数不正
      CALL_SATUS_ERR , // その他のエラー
    } CALL_SATUS
    
    CALL_STATUS MyFunction()
    {
      CALL_STATUS Stat;  
      :// 何かの処理。結果の査定
      return Stat;　// CALL_SATUS型の意味が戻る
    }
    
[/code]

の様なコードが考えられます。


CC BY-SA 3.0
2015-03-24T06:36:45.953
8309
2015-03-24T07:05:27.167
2015-03-24T07:05:27.167
3313
3793
8305
answer


考え方の参考に、ということで。

もし戻りの型が`int`の関数であれば、それは「数値」を返したいから、あなたは`int`にしたのだと思います。何かの個数だとか。現在値だとか。

そのときもしエラーであれば、その関数の戻り値として明らかにふさわしくない値（例えば`-1`や`0`）が返れば、あなたやその関数を呼ぶ他の人は「あっ、エラーだ」と気付けるでしょう。そういう値を選ぶのが良いと思います。

「`-1`と`0`（あるいは他の値）のどれ良いか」というのは、ケースバイケースなので、その関数の戻り値が意味するものに合わせて選定してください。例えばその値は通常`0`を取りうるのか否か、ということです。  
※値の選定については、それこそ標準関数の戻り値が非常に参考になると思いますので、そういった視点で標準関数を眺めてみるのも面白いと思います。

その時、戻り値（例えば`-1`）はそのままよりも、エラー値であるということを明確に定義してあげると、呼び出す人はなお嬉しいでしょう。

[code]

    #define FUNCXXX_ERROR   (-1)
    
    int Func(void) {
        int ret = 0;
        if (/* エラー判定 */) {
            return FUNCXXX_ERROR;
        }
        /* 正常時処理 */
        return ret;
    }
    
[/code]

といった具合です。`define`でなく`enum`でも同様です。  
戻り値として戻ってきた`-1`はエラーなのか正常値なのか、というところに迷ってしまいますからね。  
また、この関数が示すエラー値を変えたくなった場合も（例えば`-1`がその後正常値の含まれるようになってしまった、とか）、このようにしておけば`define`の定義値を変えてやるだけで済みます。

`char*`型に関して言えば、これは`NULL`を返してやるのほぼ一択かなあと思います。  
※上記のように明らかに不正な値を定義する場合もあるかとは思いますが。

単なる「型」をひとつ取ってみてもいろんな意味を持たせている場合があります。  
その意味に合わせたエラー値を選定して、関数を「設計」してあげてください。


CC BY-SA 3.0
2015-03-24T07:15:35.737
8312
2015-03-24T07:15:35.737
8086
8305
answer


既に出ているように「どう設計したかによる」というのが答えになりますが。

餅は餅屋。エラーはエラー関数。  
C言語ではerrno.hがお馴染みですがerrno.hがシステム定義に寄りすぎています。  
もちろんerrnoも確認して欲しいのですが。純粋なCでないことを認めつつ参考例として、MicrosoftのCOM等で使う`HRESULT型(typedefされたlong型)`を見ていましょう。
<http://www.carrona.org/winerror.html>

うーん。32bitに沢山の情報を詰め込んでいますね。  
というか詰め込みすぎです。 <http://ir9.jp/prog/ayu/win32err.htm>

HRESULTの場合は、上位2ビットで判別するようですが……大事なのは、sevenOfNine
さんが既に回答しているように「一貫性を保つ」ことです。つまりHRESULTを介したエラーコードの定義、判定マクロといったものを作って、誰でも扱えるように作られています。

[code]

    res = func(...);
    if ( result < 0 ){ ... }
    
[/code]

よりも

[code]

    if ( FAILED(res) ){ ... } 
    
[/code]

こんな感じのほうが健全なわけです(エラー設計に変更があっても。新定義のresの値がFAILEDマクロで判定結果を返してくれればよいので)。


CC BY-SA 3.0
2015-03-24T08:16:48.187
8319
2015-03-24T08:16:48.187
8335
8305
answer


こういう話をすると、いたずらに複雑にするだけで、逆に理解の妨げになるかもしれませんが、実践的なプログラムでは、こういうエラーの出し方をする関数がよくあるということで、説明したいと思います。  
関数の戻り値とべつに、ポインタの引数を使って、エラーメッセージを出します。

サンプル：剰余を返す関数

[code]

    #include <stdio.h>
    
    enum ERROR {NOERROR = 0, ZERODIVIDE};
    // ゼロ除算したら、ZERODIVIDE
    int surplus(int a, int b, enum ERROR *error) {
    // a: 被除数、b: 除数、error: エラーメッセージ
        if (b == 0) { // 除数が0だったら
            *error = ZERODIVIDE;
            return 0;
        } else {
            *error = NOERROR;
            return a % b;
        }
    }
    
    int main() {
        int x = 100; // 適宜値を変えてください。
        int y = 0; // 適宜値を変えてください。
        enum ERROR err = NOERROR;
    
        int result = surplus(x, y, &err);
    
        if (err == ZERODIVIDE) { // ゼロ除算したら
            printf("Divide by 0\n");
        } else { // エラーが出なかったら、剰余を出力
            printf("Surplus = %d\n", result);
        }
    
        return 0;
    }
    
[/code]

逆パターンとして、返り値をエラーメッセージとし、ポインタの引数で結果を渡す関数も、ありますね。


CC BY-SA 3.0
2015-03-24T08:34:46.477
8324
2015-03-24T08:34:46.477
7362
8305
answer


C99 以降のC言語ならば bool型 に対応しているので

[code]

    #include <stdbool.h>
    
[/code]

を読みこめば bool 型を使用可能です.

また,false が 0 ,true が 1 とマクロで定義されています


CC BY-SA 3.0
2015-03-24T19:42:11.070
8346
2015-03-24T19:42:11.070
7978
8305
answer


ここまでの回答が int に偏りすぎている気がするので double な場合を提示

double log(double) であれば、実数に対する自然対数の定義上  
- 負の値に対してはそもそも演算が定義されていない＝エラー  
- 非負数に対しては 負の無限大から正の無限大まで、任意の戻り値がありうる  
ということで「戻り値でエラーを表現する」ことがそもそもできません。  
なので log() の値域エラーは errno に値 EDOM を返却する仕様となっています。  
あなたの作る関数でも同じような状況になる可能性があります。

なので既に皆様の指摘があるとおり  
- どう実装したら適切かは案件によって毎回違う  
- それを考えるのが設計  
- １つのプロジェクトの中での自分の担当範囲では設計を一貫させておくべし  
です。

Java とかだと検査例外とかで実装されていたりしますが今回は C ですから  
言語の文化として暗黙の了解があるわけではなし、  
自分で決める [ことができます] [必要があります] かな。


CC BY-SA 3.0
2015-03-25T01:02:22.450
8348
2015-03-25T05:32:32.190
2015-03-25T05:32:32.190
8589
8589
8305
answer


0も-1も妥当なintなので基本的にintを戻す関数で「-1だったらエラー」の考え方はいけません。  
どうすれば良いでしょう？

まずは、エラーな状態になれない関数を作った方が良いです。  
難しいならば下記の事を考えましょう。

  1. リターン値をboolにして、intをポインターで戻す。
  2. リターン値のタイプをintではなく、複合型にしても良いです。

1の際はこんな感じです：

[code]

    int main(void) 
    {
        int  calc_result   = 0;
        bool calc_validity = false;
    
        calc_validity = calculate(&calc_result);
    
        printf ("Calculation result: %d, validity: %d", calc_result, calc_validity);
    }
    
    bool calculate(int *return_value) 
    {
        int result = 1 + 1;
        *return_value = result;
        return (result == 2) ? true : false;
    }
    
[/code]

2の場合はこんな感じ：

[code]

    typedef struct calculation_result {
        bool valid;
        int  result;
    } calculation_result;
    
    calculation_result calculate();
    
    int main(void) 
    {
        calculation_result result;
    
        result = calculate();
    
        printf ("Calculation result: %d, validity: %d", result.result, result.valid);
    }
    
    calculation_result calculate()
    {
        int result = 1 + 1;
        bool valid = ((result == 2) ? true : false);
    
        calculation_result return_value;
        return_value.valid  = valid;
        return_value.result = result;
    
        return return_value;
    }
    
[/code]

2の方が読みやすいしジェネリックですし、ポインターに関する問題もないようので2の方が何となくおすすめですね。  
コードはboolを使っているのでプレーンCのコンパイラーでビルドをするならboolをcharにマップしてください。


CC BY-SA 3.0
2015-04-30T01:58:01.777
9675
2015-09-04T14:33:16.410
2015-09-04T14:33:16.410
9544
9544
8305
answer


ライブラリでは、引数に法則性はないと言われていますが、返り値にはある程度の法則があります。ですので、`c`で用意されているライブラリに合わせることをおすすめします。標準Cライブラリでは、

処理が関数内で完結している場合(成功か失敗の二択)、成功した時に`０`を返し、失敗した場合それ以外やエラー番号などの数値を返します。

> 例：`socket(NULL,NULL,NULL);`では返り値は-1,エラー番号97となる。

処理が関数内で完結していない（関数に対して要求をする）場合、失敗した時に`0`や`NULL`を返します。

> 例：`fopen("","r");`エラー番号２  
>  `for(;malloc(long);)`エラー番号１２

エラー番号は`errno`の値です。通常は０エラーが発生した場合、エラーを示す値が代入されます。


CC BY-SA 3.0
2015-08-19T15:41:35.420
14634
2015-08-19T15:41:35.420
10972
8305
answer


色々な設計ポリシーがあると思いますが、  
戻り値に二つの意味を持たせるのは、個人的に好きではないですね。  
標準ライブラリにはそういうのが多いですけど。例えば、

[code]

    FILE *fopen(const char *path, const char *mode); 
    
[/code]

戻り値は、成功時はポインター。失敗時は NULL を返す。

[code]

    float strtof(const char *nptr, char **endptr); 
    
[/code]

この場合は戻り値で成功／失敗の判断ができないので、  
グローバル変数の errno が 0 以外の値かどうかを確認する必要がある。

こういうのが混在していると、ちょっと気持ち悪いですね^^;

私の場合は、処理の成否のみを返すように統一しています。


CC BY-SA 4.0
2018-08-12T15:25:29.677
47470
2018-08-12T16:52:38.113
2018-08-12T16:52:38.113
29364
29364
8305
answer