C言語のエラーを直して覚える
実際に出力されるメッセージを読みながら、空欄を埋めて直します。全108問。間違えた問題は翌日・3日後・7日後に自動で再出題されます。
エラー別に調べる
初級
- error: expected ';' before ... の原因と直し方文末のセミコロンを書き忘れたときに出るエラーです。エラーが指す行ではなく、その1つ前の行にセミコロンが無いことがほとんどです。
- error: expected declaration or statement at end of input の原因と直し方波括弧{}の対応が崩れ、閉じ忘れがあるときに出るエラーです。ファイルの終わりまで読んでもブロックが閉じられないため、末尾でエラーになります。
- error: 'x' undeclared (first use in this function) の原因と直し方宣言していない変数を使ったときに出るエラーです。宣言のタイプミスや、スコープの外で定義した変数を使おうとした場合に起きます。
- error: implicit declaration of function 'X' の原因と直し方標準ライブラリの関数を使う際に、対応する#includeを書き忘れたときに出るエラーです。以前のC言語では警告どまりでしたが、現在は多くの環境でエラーとして扱われます。
- error: missing terminating " character の原因と直し方文字列リテラルを開いたダブルクォートを閉じ忘れたときに出るエラーです。1行の途中で誤って別の記号を書いてしまった場合にも起きます。
- error: invalid operands to binary % の原因と直し方剰余演算子%をdouble型やfloat型に対して使おうとしたときに出るエラーです。%はint型などの整数型専用の演算子です。
- error: unknown type name の原因と直し方型名の大文字小文字を間違えたときに出るエラーです。intをIntと書いてしまうなど、キーボードの打ち間違いで起きがちです。
- error: initialization of 'char' from 'char *' makes integer from pointer without a cast の原因と直し方1文字を表すchar型の変数に、ダブルクォートで囲んだ文字列リテラルを代入しようとしたときに出るエラーです。シングルクォートとダブルクォートは別物です。
- if文の条件式で = と == を間違えて常に真になる比較のつもりで==と書くべきところを=と書いてしまうと、比較ではなく代入になり、条件は代入した値の真偽で判定されます。エラーにはならず、意図しない分岐が起こります。
- int同士の割り算で小数点以下が切り捨てられるint型同士を/で割ると、結果もint型になり小数点以下が切り捨てられます。平均値の計算などで意図せず結果が狂う典型的な原因です。
- 配列のループが1つずれて範囲外を読む・最後の要素が抜ける添字は0から要素数-1までという境界を1つ間違えると、最後の要素が抜けたり、配列の外を読んでしまったりします。
- Floating point exception(整数のゼロ除算)int型を0で割ったときに発生するクラッシュです。Cはこれを例外として捕まえられず、プログラムはその場で強制終了します。
- Segmentation fault(NULLポインタの参照)NULL(何も指していないポインタ)の指す先を読み書きしようとしたときに発生するクラッシュです。Cで最も頻出する実行時エラーです。
- printfの%dにdouble型を渡すと無関係な値が表示される%dは整数用の書式指定子です。double型の値を%dに渡すと、コンパイルは通り実行もできますが、表示される数値はまったく無関係な値になります。
中級
- sizeofが配列の要素数ではなくポインタのサイズを返す配列に対するsizeofは配列全体のバイト数を返しますが、関数の引数として渡された配列はポインタに変わっているため、同じsizeofが8(ポインタのサイズ)を返してしまいます。
- Segmentation fault(strcpyによるバッファオーバーフロー)コピー先のchar配列より長い文字列をstrcpyでコピーすると、確保した領域を超えて書き込みが起こります。隣接するメモリを破壊し、クラッシュにつながります。
- strcmpの戻り値を真偽値と勘違いして条件が逆になるstrcmpは文字列が等しいときに0を返します。if (strcmp(a, b))と書くと「等しくないとき」に真になり、多くの人が期待する「等しいとき」とは逆の分岐になります。
- ビット演算子 & の優先順位が比較演算子 == より低いことによる誤動作a & b == cと書くと、==の方が優先順位が高いため、a & (b == c)として解釈されます。意図した(a & b) == cとは異なる結果になります。
- switch文でbreakを忘れて次のcaseまで実行される(フォールスルー)各caseの末尾にbreakが無いと、一致したcase以降のブロックがそのまま次のcaseに流れ込んで実行され続けます。
- error: invalid operands to binary ==(構造体同士の==比較)構造体同士を==で比較しようとしたときに出るエラーです。Cの==は組み込み型専用で、構造体には定義されていません。
- error: assignment of read-only variable の原因と直し方constをつけて宣言した変数に、後から値を代入しようとしたときに出るエラーです。constは「一度決めたら変更不可」を意味します。
- error: assignment of read-only location の原因と直し方const int *pのように「指す先が変更不可」なポインタを介して、*pに値を書き込もうとしたときに出るエラーです。
- Segmentation fault(scanfで&を付け忘れる)scanfは書き込み先のアドレスを受け取る関数です。scanf("%d", x)のように&を付け忘れると、変数の値そのものを不正なアドレスとして書き込もうとし、クラッシュします。
- 多次元配列で行と列の添字を取り違えて違う値を読むarr[row][col]とarr[col][row]は多くの場合どちらもコンパイルは通り実行もできますが、正方でない配列では違う位置の値を読んでしまいます。
- 関数内のローカル変数のアドレスを返すと解放済み領域を指す関数の中で宣言したローカル変数は、その関数を抜けると同時に領域が無効になります。そのアドレスをreturnで返すと、呼び出し元は既に無効な領域を指すポインタを受け取ります。
- 符号ありと符号なしの比較で -1 が巨大な数として扱われるint型とunsigned int型を比較すると、int型の値は自動的にunsigned intへ変換されます。負の数は非常に大きな正の数として扱われ、直感に反する比較結果になります。
- int型の最大値に1を足すと最小値(負の数)に折り返るINT_MAXに1を足すと、多くの環境ではINT_MINまで折り返り、突然大きな正の数が大きな負の数に変わります。
上級
- free(): double free detected in tcache の原因と直し方同じポインタに対してfree()を2回呼び出したときに検出されるエラーです。メモリ管理システムが不整合を検知して強制終了します。
- Assertion failed の原因と直し方assert()に渡した条件式がその場でfalseになったときに強制終了するマクロです。「本来ありえないはずの状態」を検出するために使われます。
- Segmentation fault(再帰関数の終了条件忘れによるスタックオーバーフロー)再帰関数に正しい終了条件(ベースケース)が無いと、関数呼び出しのたびにスタック領域が積み上がり続け、上限に達した時点でクラッシュします。
- error: invalid type argument of unary '*'(関数ポインタ呼び出しの優先順位)*fp(a, b)と書くと、関数呼び出し()の方が間接参照*より優先順位が高いため、fp(a, b)を先に呼び出してからその戻り値に*を適用しようとしてしまいます。
- error: invalid type argument of '->' の原因と直し方構造体そのものの変数に対して->を使おうとしたときに出るエラーです。->は構造体へのポインタ専用の演算子です。
- 0.1 + 0.2 == 0.3 が偽になる理由doubleやfloatは2進数で値を表現するため、0.1や0.2のような小数を正確には表現できません。==でぴったり比較すると、ほぼ確実に一致しません。
- free()した後のポインタを使っても動いてしまう(use-after-free)free()で解放したメモリに対して読み書きを続けても、多くの場合その場ではクラッシュせず、直前まで入っていた値がそのまま読めてしまいます。
- Segmentation fault(配列の範囲外への書き込み)配列の宣言サイズを大きく超えた添字に書き込むと、プロセスに割り当てられていないメモリ領域に触れてクラッシュします。
- static付きローカル変数が関数呼び出しをまたいで値を保持し続ける関数内でstaticを付けて宣言した変数は、関数を抜けても値が消えません。次回その関数が呼ばれたときも、前回の値から処理が続きます。