第3章:最初のCプログラム
この章で学ぶこと
- Hello Worldプログラムの構造
- コンパイルの方法
- プログラムの実行
- エラーメッセージの読み方
- デバッグの基本
---
3.1 Hello World
プログラマーの伝統
新しいプログラミング言語を学ぶとき、最初に書くプログラムは「Hello, World!」と表示するものです。これは1978年に出版された「The C Programming Language」(通称K&R本)で紹介されて以来、世界中のプログラマーの伝統となっています。
コードを書く
前の章で作成した hello.c ファイルを使用します。まだ作成していない場合は、以下の手順で作成してください。
$ cd ~/42/c_beginner
$ vim hello.c
以下のコードを入力します:
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n");
return 0;
}
コードの解説
1行ずつ見ていきましょう。
#include
#include <stdio.h>
これはプリプロセッサディレクティブと呼ばれます。
#include: 別のファイルを取り込む指示: Standard Input/Output(標準入出力)のヘッダーファイルprintf 関数を使うために必要簡単に言うと、「printf を使いたいので、その説明書を読み込んでください」という意味です。
int main(void)
int main(void)
これはmain関数の定義です。
int: この関数が整数を返すことを示すmain: 関数の名前(特別な名前)void: 引数を取らないことを示すmain関数はプログラムの入口です。Cプログラムは必ずmain関数から実行が始まります。
波括弧 { と }
{
...
}
波括弧は、関数の本体(実行する処理)を囲みます。
printf("Hello, World!\n");
printf("Hello, World!\n");
printf: 画面に文字を表示する関数"Hello, World!\n": 表示する文字列\n: 改行(New Line)を表す特殊文字;: 文の終わりを示すreturn 0;
return 0;
return: 関数から値を返す0: 正常終了を意味する値main関数が 0 を返すと、「プログラムは正常に終了しました」という意味になります。
---
3.2 コンパイル
コンパイラとは
C言語のソースコード(.c ファイル)は、そのままでは実行できません。コンパイラを使って機械語に変換する必要があります。
[hello.c] → [コンパイラ] → [a.out(実行ファイル)]
gccコンパイラ
最も広く使われているCコンパイラはgcc(GNU Compiler Collection)です。
インストール確認
$ gcc --version
Apple clang version 14.0.0 (clang-1400.0.29.202)
...
バージョン情報が表示されれば、gccが使える状態です。
Macでは gcc コマンドは実際には clang というコンパイラを呼び出しますが、使い方はほぼ同じです。
gccがインストールされていない場合
Mac: Xcodeコマンドラインツールをインストール
$ xcode-select --install
Ubuntu/WSL: build-essentialパッケージをインストール
$ sudo apt update
$ sudo apt install build-essential
コンパイルの実行
$ gcc hello.c
エラーが表示されなければ、コンパイル成功です。
$ ls
a.out hello.c
a.out という実行ファイルが作成されました。
出力ファイル名の指定
-o オプションで、出力ファイルの名前を指定できます。
$ gcc hello.c -o hello
$ ls
hello hello.c
hello という実行ファイルが作成されました。
---
3.3 プログラムの実行
実行方法
作成した実行ファイルを実行するには、パスを指定して呼び出します。
$ ./hello
Hello, World!
./ は「現在のディレクトリの」という意味です。これを付けないと、システムのコマンドディレクトリ(/usr/bin など)から探してしまいます。
実行の流れ
./helloと入力してEnter- OSが
hello実行ファイルをメモリに読み込む - main関数から実行開始
printfが "Hello, World!\n" を画面に表示return 0でプログラム終了- プロンプトに戻る
たったこれだけですが、コンピュータ内部では膨大な処理が行われています。
終了ステータスの確認
プログラムの終了ステータス(return の値)は、特殊変数 $? で確認できます。
$ ./hello
Hello, World!
$ echo $?
0
0 は正常終了を意味します。エラーが発生した場合は、0 以外の値が返されます。
---
3.4 コンパイルエラー
エラーは怖くない
コードを書いていると、必ずエラーが発生します。エラーメッセージは友達です。何が問題かを教えてくれています。
典型的なエラーとその対処
セミコロンの欠落
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n") // セミコロンがない
return 0;
}
コンパイル:
$ gcc hello.c
hello.c:6:5: error: expected ';' after expression
return 0;
^
;
1 error generated.
読み方:
hello.c:6:5: hello.cの6行目、5文字目error: エラーexpected ';' after expression: 式の後にセミコロンが必要
エラーメッセージは「6行目」を指していますが、実際の問題は5行目です。コンパイラは「6行目まで来て、前の行にセミコロンがないことに気づいた」のです。
対処: 5行目の末尾に ; を追加
括弧の不一致
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n"; // ) がない
return 0;
}
$ gcc hello.c
hello.c:5:30: error: expected ')'
printf("Hello, World!\n";
^
hello.c:5:11: note: to match this '('
printf("Hello, World!\n";
^
1 error generated.
対処: "Hello, World!\n" の後に ) を追加
引用符の閉じ忘れ
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n); // " が閉じていない
return 0;
}
$ gcc hello.c
hello.c:5:12: error: missing terminating '"' character
printf("Hello, World!\n);
^
hello.c:6:5: error: expected expression
return 0;
^
2 errors generated.
対処: 文字列の末尾に " を追加
未定義の関数
#include <stdio.h>
int main(void)
{
prntf("Hello, World!\n"); // printf のスペルミス
return 0;
}
$ gcc hello.c
hello.c:5:5: error: implicit declaration of function 'prntf' is invalid in C99
prntf("Hello, World!\n");
^
1 error generated.
対処: prntf を printf に修正
ヘッダーファイルの欠落
// #include <stdio.h> を書き忘れた
int main(void)
{
printf("Hello, World!\n");
return 0;
}
$ gcc hello.c
hello.c:4:5: error: implicit declaration of function 'printf' is invalid in C99
printf("Hello, World!\n");
^
1 error generated.
対処: ファイルの先頭に #include を追加
エラーを読むコツ
---
3.5 警告(Warning)
エラーと警告の違い
警告を表示するオプション
gcc にはデフォルトで表示されない警告があります。-Wall オプションで多くの警告を表示できます。
$ gcc -Wall hello.c -o hello
-Wall は "Warning all" の意味...と思いきや、実際には「すべて」ではありません。さらに厳しいチェックをするには:
$ gcc -Wall -Wextra -Werror hello.c -o hello
-Wall: 一般的な警告を有効にする-Wextra: 追加の警告を有効にする-Werror: 警告をエラーとして扱う警告の例
#include <stdio.h>
int main(void)
{
int x; // 初期化していない
printf("%d\n", x); // 未初期化の変数を使用
return 0;
}
$ gcc -Wall hello.c
hello.c:6:20: warning: variable 'x' is uninitialized when used here
printf("%d\n", x);
^
hello.c:5:10: note: initialize the variable 'x' to silence this warning
int x;
^
= 0
対処: int x = 0; と初期化する
警告を無視しない
警告は「コンパイルは通るけど、問題があるかもしれないよ」というメッセージです。無視せずに対処しましょう。
42のプロジェクトでは、警告が残っていると減点対象になることがあります。
---
3.6 コンパイルプロセスの詳細
4つの段階
C言語のコンパイルは、実は4つの段階を経ています。
[ソースコード] → [プリプロセス] → [コンパイル] → [アセンブル] → [リンク] → [実行ファイル]
1. プリプロセス
#include や #define などのプリプロセッサディレクティブを処理します。
$ gcc -E hello.c -o hello.i
hello.i には、stdio.h の内容が展開されたコードが含まれています。
2. コンパイル
Cコードをアセンブリ言語に変換します。
$ gcc -S hello.c -o hello.s
hello.s にはアセンブリコードが含まれています:
.section __TEXT,__text,regular,pure_instructions
.globl _main
_main:
pushq %rbp
movq %rsp, %rbp
...
3. アセンブル
アセンブリコードを機械語(オブジェクトファイル)に変換します。
$ gcc -c hello.c -o hello.o
hello.o はバイナリファイルで、人間が読むことはできません。
4. リンク
オブジェクトファイルとライブラリを結合して、実行ファイルを作成します。
$ gcc hello.o -o hello
通常は gcc hello.c -o hello とするだけで、4つの段階すべてが自動で行われます。
---
3.7 Makefileの基礎
なぜMakefileが必要か
複数のファイルからなるプロジェクトでは、コンパイルコマンドが長く複雑になります。
$ gcc -Wall -Wextra -Werror file1.c file2.c file3.c -o myprogram
毎回これを入力するのは大変です。Makefile を使うと、簡単なコマンドでコンパイルできるようになります。
最小限のMakefile
プロジェクトディレクトリに Makefile という名前のファイルを作成します。
$ vim Makefile
hello: hello.c
gcc -Wall -Wextra -Werror hello.c -o hello
重要: インデントはタブ文字でなければなりません。スペースではエラーになります。
vimでタブを入力するには、Ctrl + V の後に Tab キーを押します。
Makefileの使い方
$ make
gcc -Wall -Wextra -Werror hello.c -o hello
make コマンドを実行するだけで、Makefileに書かれた処理が実行されます。
Makefileの基本構造
ターゲット: 依存ファイル
コマンド
もう少し実用的なMakefile
CC = gcc
CFLAGS = -Wall -Wextra -Werror
hello: hello.c
$(CC) $(CFLAGS) hello.c -o hello
clean:
rm -f hello
re: clean hello
CC: 使用するコンパイラCFLAGS: コンパイルオプションclean: 生成ファイルを削除re: クリーンしてから再コンパイル$ make # hello をコンパイル
$ make clean # hello を削除
$ make re # 削除してから再コンパイル
---
3.8 実践:プログラムを改造してみよう
課題1: 自分の名前を表示する
hello.c を修正して、自分の名前を表示するプログラムを作成してください。
#include <stdio.h>
int main(void)
{
printf("Hello, [あなたの名前]!\n");
return 0;
}
課題2: 複数行を表示する
複数の printf を使って、複数行を表示してみましょう。
#include <stdio.h>
int main(void)
{
printf("Line 1\n");
printf("Line 2\n");
printf("Line 3\n");
return 0;
}
課題3: 特殊文字を使う
\n 以外の特殊文字(エスケープシーケンス)を試してみましょう。
| 表記 | 意味 |
|------|------|
| \n | 改行 |
| \t | タブ |
| \\ | バックスラッシュ |
| \" | ダブルクォート |
#include <stdio.h>
int main(void)
{
printf("Name:\tTaro\n");
printf("Age:\t25\n");
printf("Quote:\t\"Hello!\"\n");
return 0;
}
出力:
Name: Taro
Age: 25
Quote: "Hello!"
課題4: わざとエラーを起こしてみる
セミコロンを削除したり、スペルミスをしたりして、意図的にエラーを発生させ、エラーメッセージを読む練習をしましょう。
エラーと仲良くなることが、プログラミング上達の秘訣です。
---
3.9 この章のまとめ
学んだこと
- Hello Worldの構造
#include : ヘッダーファイルの読み込み
- int main(void): プログラムの入口
- printf(): 画面への出力
- return 0: 正常終了- コンパイル
gcc ファイル名.c -o 出力名
- -Wall -Wextra -Werror オプション- 実行
./プログラム名- エラー対処
- Makefile
コマンドまとめ
$ gcc hello.c -o hello # コンパイル
$ gcc -Wall -Wextra -Werror hello.c -o hello # 警告を有効に
$ ./hello # 実行
$ make # Makefileを使ったコンパイル
$ make clean # クリーンアップ
次の章の予告
次章では、変数と型について学びます。数値や文字を保存し、計算する方法を覚えましょう。
---
Column: 最初のプログラマーたち
世界最初のプログラマー
世界最初のプログラマーは、19世紀のイギリスの数学者エイダ・ラブレス(Ada Lovelace)と言われています。
チャールズ・バベッジが設計した「解析機関」という機械式計算機のためのプログラムを書きました。実際には解析機関は完成しませんでしたが、彼女の残したノートは、現代のプログラミングの概念を先取りしていました。
Hello Worldの起源
「Hello, World!」プログラムは、1978年にブライアン・カーニハンとデニス・リッチーが書いた「The C Programming Language」で紹介されました。
当時のバージョンは:
main()
{
printf("hello, world\n");
}
現代のC言語とは少し異なりますね。#include がなく、main の戻り値の型も指定されていません。C言語も進化してきたのです。
なぜ「Hello, World」なのか
特に深い意味はないようです。カーニハンによると、「何か表示する例が必要だったので、適当に選んだ」とのこと。
しかし、この単純なプログラムが50年近く、世界中のプログラマーの最初の一歩となっています。あなたが今日書いた printf("Hello, World!\n"); は、何百万人ものプログラマーが書いてきたものと同じです。
プログラミングの世界へようこそ!
---
確認問題
問題1
以下のコードで、画面に表示される文字列は何ですか?printf("Hello\tWorld\n");
解答
Hello World
(\t はタブ、\n は改行)問題2
gccでコンパイル時にすべての警告を表示するオプションは何ですか?解答
-Wall(さらに厳しくするなら -Wall -Wextra -Werror)
問題3
コンパイルして生成された実行ファイルprogram を実行するコマンドは何ですか?解答
./program
問題4
以下のコードにはエラーがあります。何が問題ですか?#include <stdio.h>
int main(void)
{
printf("Hello, World!\n")
return 0;
}
解答
printf の行の末尾にセミコロン(;)がない。
正しくは:printf("Hello, World!\n");
問題5
main 関数の return 0; は何を意味しますか?解答
プログラムが正常に終了したことを示す。
0 は「成功」を意味し、0 以外の値はエラーや異常終了を意味する。
---
次の章では、変数と型について学びます。数値を扱えるようになりましょう!