web-dev-qa-db-ja.com

Cのブール関数からエラーを返すにはどうすればよいですか?

私はCで小さなペットプログラムに取り組んでいます。そこでは、一連の正方形で構成されるゲームボードを持っています。

typedef struct _square {
    bool checked;
} Square;

typedef struct _board {
    Square *squares;
    uint8_t width;
    uint8_t height;
} Board;

これらの構造体を公開したくないので、代わりに、正方形がチェックされているかどうかをチェックする、square_is_checkedという単純な関数を作成します。戻り値の型がboolであることは理にかなっています(ここではC99を想定しています)。

bool square_is_checked(Board *b, uint8_t row, uint8_t col)
{
    return b->squares[(uint16_t)row * b->height + col].checked;
}

ただし、NULLポインター、または存在しない行と列が引数として渡される可能性があります。だから私はいくつかの簡単なチェックを追加したいと思います:

bool square_is_checked(Board *b, uint8_t row, uint8_t col)
{
    if (!b) { return ???; }
    if (row >= b->height || col >= b->width) { return ???; }
    return b->squares[(uint16_t)row * b->height + col].checked;
}

ただし、booltrueまたはfalseのどちらかであり、エラー管理の余地はありません。ここでベストプラクティスと見なされるものは何ですか(たとえば、boolへのポインターを渡すためにこの関数を書き直す必要がありますか)?

2
tonysdg

これは、実際にはCの関数からboolを返す方法に関するものではありません。コードはすでにそれを実行しています。

問題は、実際には、可能な値のセットがboolおよびtrueより大きい場合の正しいデータ型はfalseであるということです。それは長い哲学的な議論の対象となる可能性がありますが、これは個人的なプロジェクトであるため、より大きな文脈での決定について心配する必要はありません。チームでは、既存のコードがこのような状況についてすでに意見を持っている可能性があるため、決定が下されることがあります。

したがって、この場合、考えられる答えはいくつかあります。いくつかのオプション:

  • 戻り値の型はboolのままにし、ヌルポインターや無効な値(row > heightなど)などの入力エラーにはfalseを使用します
  • 列挙型を使用して、ブール値の代わりにCHECKEDUNCHECKEDUNKNOWNなどを表します。多くのCライブラリは、エラー(または-1)を示すために0(例:INVALID_SOCKET)を返すことでこれを効果的に行います。
  • この関数が呼び出されたときに常に意味のあるブール値を正しく返すことができるように、他の場所で入力を検証してください。
8
Dan1701

その他のオプションには、boolへのポインターを渡すこと、およびエラー値を返すことが含まれます。このようなもの:

typedef enum square_err_t {
    ERROR_NONE = 0,
    ERROR_BAD_PTR,
    ERROR_BAD_ROW,
    ERROR_BAD_COL
} square_err_t;

square_err_t square_is_checked(Board* b, uint8_t row, uint8_t col, bool* is_checked)
{
    ...
}

これは個人的なプロジェクトであるため、assertを使用して前提条件としてチェックを行うことができる場合があります。

bool square_is_checked(Board* b, uint8_t row, uint8_t col)
{
    assert(b != NULL);
    assert(col < b->width);
    assert(row < b->height);
    ...etc.
}

これは開発中にエラーをキャッチしますが、チェックはリリースでコンパイルされます。

コンパイラまたは他のツールがそれをサポートしている場合は、関数に nullability注釈 で注釈を付けることができる場合があります。 (この例はSwiftの場合ですが、他の言語でも使用できるツールをクイック検索で表示します。)これらにより、関数への引数をnonnullとしてマークできます。つまり、引数をNULLにすることはできません。これにより、実行時ではなくコンパイル時に潜在的な呼び出しがキャッチされ、出荷前に修正できます。

6
user1118321

あなたがするべき基本的な決定があります:

無効な入力は契約違反ですか、それとも予想されますか?

あなたはすでにBoardへのポインターを与えるために呼び出し元に依存しており、正しいものをそれに向けています。なぜ無効な入力NULLを特別なケースにしたいのですか、それはプログラマによるすべての論理エラーではありませんか?

範囲外の取締役会の立場は、はるかに多くについて議論することができます:

  1. プログラマーが論理エラーを犯したのでしょうか?
  2. ユーザー入力が検証に失敗していますか?
  3. 特定のボードの論理的な継続はありますか?

論理エラーに対応する方法は3つあります。

  1. パニック。デバッグビルドでのassert()(リリースでは何もしないので、assertで寛大に)、リリースではabort()。エラーの重大度に応じて、例外が未チェックの例外をスローする中程度に安全な言語では、条件が満たされる場合があります。
  2. サイコロが落ちるところに行きましょう。それがUndefined Behaviorの意味であり、最高の効率を可能にします。
  3. 賢明なデフォルトに置き換えて混乱させたり、エラーを返したりしてください。例外のある言語では、例外はエラーを返す方法となる場合があります。
2
Deduplicator