Cb言語を学習する際によく遭遇する間違いと、その解決方法をまとめました。
❌ 間違い
int x = 10
int y = 20
エラーメッセージ
error: Expected ';' after statement
✅ 正しい
int x = 10;
int y = 20;
解説: Cbでは全ての文の末尾にセミコロンが必要です。
❌ 間違い
struct Point {
int x;
int y;
}
int main() {
return 0;
}
エラーメッセージ
error: Expected ';' after struct definition
✅ 正しい
struct Point {
int x;
int y;
};
int main() {
return 0;
}
解説: 構造体定義の後には必ずセミコロンが必要です。
❌ 間違い
int main() {
if (true) {
println("test");
// 閉じ括弧忘れ
return 0;
}
エラーメッセージ
error: Expected '}' to match '{'
✅ 正しい
int main() {
if (true) {
println("test");
}
return 0;
}
ヒント: インデントを正しく使うと、括弧の対応が見やすくなります。
❌ 間違い
int main() {
int x = 42;
string s = x; // intをstringに代入できない
return 0;
}
エラーメッセージ
error: Cannot assign 'int' to 'string'
✅ 正しい
int main() {
int x = 42;
string s = "42"; // 文字列リテラルを使用
return 0;
}
❌ 間違い(警告が出る)
int main() {
unsigned int x = -10; // 負値は0にクランプされる
println(x); // 0
return 0;
}
警告メッセージ
warning: Negative value clamped to 0 for unsigned type
✅ 正しい
int main() {
unsigned int x = 10; // 正の値を使用
println(x); // 10
return 0;
}
❌ 間違い
int main() {
int[5] arr1 = [1, 2, 3, 4, 5];
int[10] arr2 = arr1; // サイズが異なる
return 0;
}
エラーメッセージ
error: Array size mismatch: expected 10, got 5
✅ 正しい
int main() {
int[5] arr1 = [1, 2, 3, 4, 5];
int[5] arr2 = arr1; // 同じサイズ
return 0;
}
すべての文字列は\0(null終端文字)で終わる必要があります。
❌ 間違い: null終端文字を忘れる
void copy_string(string dest, string src) {
int i = 0;
while (src[i] != '\0') {
dest[i] = src[i];
i = i + 1;
}
// dest[i] = '\0'; を忘れている!
}
int main() {
string original = "Hello";
string copy;
copy_string(copy, original);
println(copy); // 不定動作: null終端文字がない
return 0;
}
✅ 正しい: null終端文字を追加
void copy_string(string dest, string src) {
int i = 0;
while (src[i] != '\0') {
dest[i] = src[i];
i = i + 1;
}
dest[i] = '\0'; // null終端文字を追加
}
int main() {
string original = "Hello";
string copy;
copy_string(copy, original);
println(copy); // Hello
return 0;
}
重要:
- 文字列をコピーする際は必ずnull終端文字も含める
- 文字列の長さを計算する際はnull終端文字まで読む
- 文字列の比較も両方がnull終端文字に達したか確認する
❌ 危険: バッファオーバーフロー
void bad_string_copy(string dest, string src) {
int i = 0;
// destのサイズを考慮していない
while (src[i] != '\0') {
dest[i] = src[i]; // destが小さすぎると境界外アクセス
i = i + 1;
}
dest[i] = '\0';
}
int main() {
string small; // サイズが不足している可能性
string large = "This is a very long string";
bad_string_copy(small, large); // 危険!
return 0;
}
✅ 正しい: サイズを確認
void safe_string_copy(string dest, string src, int max_size) {
int i = 0;
// max_size - 1 まで (null終端文字用に1つ残す)
while (src[i] != '\0' && i < max_size - 1) {
dest[i] = src[i];
i = i + 1;
}
dest[i] = '\0';
}
int main() {
string small;
string large = "Hello";
safe_string_copy(small, large, 100); // 最大サイズを指定
println(small); // Hello
return 0;
}
❌ 実行時エラー
int main() {
int[5] arr = [1, 2, 3, 4, 5];
int x = arr[10]; // 範囲外
return 0;
}
エラーメッセージ
error: Array index out of bounds: index 10, size 5
✅ 正しい
int main() {
int[5] arr = [1, 2, 3, 4, 5];
int x = arr[4]; // 有効な範囲(0-4)
return 0;
}
ヒント: 配列のインデックスは0から始まり、サイズ-1までです。
❌ 実行時エラー
int main() {
int x = 100;
int y = 0;
int z = x / y; // ゼロ除算
return 0;
}
エラーメッセージ
error: Division by zero
✅ 正しい
int main() {
int x = 100;
int y = 0;
if (y != 0) {
int z = x / y;
println(z);
} else {
println("Error: Cannot divide by zero");
}
return 0;
}
❌ 実行時エラー
int main() {
int* ptr = nullptr;
int x = *ptr; // nullポインタ参照
return 0;
}
エラーメッセージ
error: Null pointer dereference
✅ 正しい
int main() {
int* ptr = nullptr;
if (ptr != nullptr) {
int x = *ptr;
println(x);
} else {
println("Error: Pointer is null");
}
return 0;
}
❌ 危険
int main() {
int* ptr; // 未初期化
*ptr = 42; // 不定なアドレスへのアクセス
return 0;
}
✅ 正しい
int main() {
int value = 0;
int* ptr = &value; // 初期化
*ptr = 42;
println(*ptr); // 42
return 0;
}
❌ 危険
int* get_pointer() {
int local = 42;
return &local; // ローカル変数のアドレスを返す(危険)
}
int main() {
int* ptr = get_pointer();
int x = *ptr; // 不正なメモリアクセス
return 0;
}
✅ 正しい(代替案1: 値を返す)
int get_value() {
int local = 42;
return local; // 値をコピーして返す
}
int main() {
int x = get_value();
println(x); // 42
return 0;
}
✅ 正しい(代替案2: 参照渡し)
void set_value(int& ref) {
ref = 42;
}
int main() {
int x = 0;
set_value(x);
println(x); // 42
return 0;
}
❌ エラー
int main() {
int value = 42;
const int* ptr = &value;
*ptr = 100; // constポインタ経由の変更は禁止
return 0;
}
エラーメッセージ
error: Cannot modify through const pointer
✅ 正しい
int main() {
int value = 42;
const int* ptr = &value;
int x = *ptr; // 読み取りはOK
println(x); // 42
value = 100; // 元の変数の変更はOK
return 0;
}
❌ エラー: const変数への非constポインタ
int main() {
const int value = 42;
int* ptr = &value; // エラー: const変数への非constポインタは禁止
*ptr = 100; // これを許すとconst制約が破られる
return 0;
}
エラーメッセージ
error: Cannot take non-const pointer to const variable
理由: const変数の値は外部から変更されてはいけません。非constポインタを許すと、そのポインタ経由でconst変数の値を変更できてしまい、const制約が無意味になります。
✅ 正しい: constポインタを使用
int main() {
const int value = 42;
const int* ptr = &value; // OK: constポインタなら許可
int x = *ptr; // 読み取りはOK
println(x); // 42
// *ptr = 100; // エラー: constポインタ経由の変更は禁止
return 0;
}
*constは「ポインタ自体が定数」を意味し、ポインタが指すアドレスを変更できません。
*❌ エラー: constポインタのアドレス変更
int main() {
int a = 10;
int b = 20;
int *const ptr = &a; // アドレス変更不可
*ptr = 15; // OK: 値の変更は可能
ptr = &b; // エラー: アドレスの変更は禁止
return 0;
}
エラーメッセージ
error: Cannot reassign const pointer
✅ 正しい
int main() {
int a = 10;
int b = 20;
int *const ptr = &a; // アドレス変更不可
*ptr = 15; // OK: 値の変更は可能
println(*ptr); // 15
// 新しいポインタが必要な場合は別の変数を使う
int* ptr2 = &b;
println(*ptr2); // 20
return 0;
}
const修飾子の組み合わせ
int main() {
int value = 42;
const int value2 = 100;
// パターン1: 値が定数
const int* ptr1 = &value;
// ptr1 = &value2; // OK: アドレス変更可能
// *ptr1 = 50; // エラー: 値の変更は不可
// パターン2: アドレスが定数
int *const ptr2 = &value;
// ptr2 = &value; // エラー: アドレス変更不可
// *ptr2 = 50; // OK: 値の変更は可能
// パターン3: 値もアドレスも定数
const int *const ptr3 = &value2;
// ptr3 = &value; // エラー: アドレス変更不可
// *ptr3 = 50; // エラー: 値の変更も不可
return 0;
}
❌ 間違い
int main() {
int[5] arr = [1, 2, 3]; // 要素数が足りない
return 0;
}
エラーメッセージ
error: Array initializer size mismatch
✅ 正しい
int main() {
int[5] arr = [1, 2, 3, 4, 5]; // 正確に5個
return 0;
}
❌ 間違い
int main() {
int[2][3] matrix = [1, 2, 3, 4, 5, 6]; // フラットな配列
return 0;
}
✅ 正しい
int main() {
int[2][3] matrix = [
[1, 2, 3],
[4, 5, 6]
];
return 0;
}
❌ 間違い
int get_value() {
int x = 42;
// returnを忘れた
}
int main() {
int value = get_value(); // 戻り値が不定
return 0;
}
エラーメッセージ
error: Missing return statement in non-void function
✅ 正しい
int get_value() {
int x = 42;
return x;
}
int main() {
int value = get_value();
println(value); // 42
return 0;
}
❌ 間違い
void print_int(int x) {
println(x);
}
int main() {
string s = "hello";
print_int(s); // stringをintに渡せない
return 0;
}
エラーメッセージ
error: Type mismatch in function call: expected 'int', got 'string'
✅ 正しい
void print_int(int x) {
println(x);
}
int main() {
int i = 42;
print_int(i);
return 0;
}
Cbでは構造体の初期化は必ずリテラル初期化を使用してください。フィールドごとの代入は非推奨です。
❌ 非推奨: フィールドごとの代入
struct Point {
int x;
int y;
};
int main() {
Point p;
p.x = 10; // 非推奨
p.y = 20; // 非推奨
return 0;
}
✅ 推奨: リテラル初期化
struct Point {
int x;
int y;
};
int main() {
Point p = {10, 20}; // 推奨: リテラル初期化
println(p.x); // 10
println(p.y); // 20
return 0;
}
✅ 関数でも同様
struct Task {
int task_id;
int priority;
int callback_type;
void* data;
};
Task create_task(int id, int priority, int callback_type) {
// ✅ リテラル初期化を使用
return {id, priority, callback_type, nullptr};
}
int main() {
Task t = create_task(1, 5, 0);
println("Task ID: ", t.task_id);
return 0;
}
✅ 構造体の更新もリテラルで
struct Config {
int width;
int height;
bool fullscreen;
};
void update_config(Config& cfg, int w, int h) {
// ✅ 新しいリテラルを作成して代入
cfg = {w, h, false};
}
int main() {
Config cfg = {800, 600, false};
update_config(cfg, 1920, 1080);
println("Width: ", cfg.width); // 1920
return 0;
}
理由:
- リテラル初期化は最適化されており、高速で安全
- フィールドごとの代入は冗長で、バグの温床になりやすい
- コードが簡潔で読みやすくなる
- インタプリタの実装も簡潔になる
注意: リテラル初期化ができない場合は、インタプリタの実装不足です。その場合は実装の改善が必要です。
❌ 問題のあるコード
struct Point {
int x;
int y;
};
int main() {
Point p; // 未初期化
println(p.x); // 未初期化の値
return 0;
}
✅ 正しい: リテラル初期化
struct Point {
int x;
int y;
};
int main() {
Point p = {0, 0}; // リテラル初期化
println(p.x); // 0
return 0;
}
struct Data {
int[100] values;
};
int main() {
Data d1;
Data d2 = d1; // 大きな構造体のコピー(遅い)
return 0;
}
✅ 推奨(参照渡し)
struct Data {
int[100] values;
};
void process_data(Data& d) {
// 参照渡しでコピーを避ける
d.values[0] = 42;
}
int main() {
Data d;
process_data(d);
return 0;
}
❌ 非効率
int sum_array(int[1000] arr) { // 配列全体をコピー
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += arr[i];
}
return sum;
}
✅ 効率的
int sum_array(int[1000]& arr) { // 参照渡し(コピーなし)
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += arr[i];
}
return sum;
}
❌ スタックオーバーフローの危険
int bad_recursion(int n) {
return bad_recursion(n - 1); // 終了条件がない
}
✅ 正しい
int factorial(int n) {
if (n <= 1) {
return 1; // 終了条件
}
return n * factorial(n - 1);
}
エラーメッセージには以下の情報が含まれます:
- ファイル名と行番号: エラーの場所
- エラーの種類: 何が問題なのか
- コード抜粋: エラー箇所の前後
例:
test.cb:3:13: error: Undefined variable 'unknown_var'
2 | int x = 10;
3 | int y = unknown_var;
| ^
4 | return 0;
# 英語デバッグ
./main --debug program.cb
# 日本語デバッグ
./main --debug-ja program.cb- 大きなプログラムを小さな部分に分割
- 各部分を個別にテスト
- 動作を確認してから統合
int main() {
int x = 10;
println("Debug: x =", x); // 変数の値を確認
x = x * 2;
println("Debug: after multiplication, x =", x);
return 0;
}
- セミコロン忘れ → 文末には必ずセミコロン
- 配列の境界外アクセス → インデックスの範囲を確認
- 型の不一致 → 変数の型を意識する
- return文の忘れ → 戻り値のある関数は必ずreturn
- 未初期化変数 → 変数は使用前に初期化
- エラーメッセージを読む
- 該当行を確認する
- 似た動作する例と比較する
- デバッグモードで実行する
- 小さな例で再現する
エラーは学習の機会です。諦めずにトライしましょう! 💪