言語
日本語
English

Caution

お使いのブラウザはJavaScriptが無効になっております。
当サイトでは検索などの処理にJavaScriptを使用しています。
より快適にご利用頂くため、JavaScriptを有効にしたうえで当サイトを閲覧することをお勧めいたします。

C言語辞典

  1. トップページ
  2. C言語辞典
  3. 初心者向け: 概要と特徴、学習順ガイド

初心者向け: 概要と特徴、学習順ガイド

C言語の文法・特徴・学習順を整理した入口ページです。

C言語の歴史・系譜・設計思想といった背景は、IT用語 & 歴史辞典の「C言語」で詳しく解説しています。本ページはC言語を書くための実用情報に絞っています。

C言語とは何か

C言語は、1972年にAT&Tベル研究所のデニス・リッチー様(Dennis Ritchie)が開発したプログラミング言語です。もともとUNIXオペレーティングシステムを書くために設計された実用言語で、2026年現在もOSカーネル・組み込みシステム・デバイスドライバ・高性能ライブラリの領域で第一線で使われています。

規格制定年主な追加
C89 / C901989 / 1990最初のANSI標準。プロトタイプ宣言、void型、列挙型
C991999//コメント、long long、可変長配列、stdint.h
C112011マルチスレッド、_Generic、無名構造体
C172018C11のバグ修正中心、新機能はほぼなし
C232024nullptr、属性、typeofconstexpr

ファイル拡張子

C 言語のソースコードは以下の拡張子のファイルに保存します。

  • .c — C ソースファイル
  • .h — ヘッダファイル(関数宣言・マクロ・型定義など)

最初に知っておく 6 つのポイント

C言語を読み書きする前に、他言語と違って戸惑うポイントを 6 つだけ先に押さえておきます。詳細は次節以降で深掘りします。

  • 文字列型がない。文字列は『char』型の配列で表現し、末尾のヌル文字『\0』で終わりを示す
  • ポインタを直接操作する。変数のメモリアドレスを『*』で宣言し、『&』でアドレスを取得する。C言語の最大の特徴
  • メモリ管理は手動。『malloc()』で確保したメモリは必ず『free()』で解放する必要がある
  • 配列の範囲チェックがない。範囲外アクセスはコンパイルエラーにならず、実行時に未定義動作を起こす
  • コンパイルが必要。ソースファイル(.c)を『gcc』でコンパイルして実行ファイルを生成してから実行する
  • ヘッダファイルと実装ファイルが分かれる。関数の宣言は『.h』ファイル、定義は『.c』ファイルに書く構成が基本

これらの背景にある設計思想(UNIX哲学、「プログラマーを信頼する」原則、UNIXからの系譜)の歴史的経緯は IT用語 & 歴史辞典 / C言語 で詳しく解説しています。

C言語の特徴

C言語にはいくつかの際立った特徴があります。これらは長所でもあり、扱う際の注意点でもあります。

低レベルアクセス

C言語はメモリのアドレスを直接操作できます。ポインタ(pointer)という仕組みを使ってメモリ上の場所を指定し、ハードウェアに近い処理が書けます。これはデバイスドライバや組み込みシステムで必須の機能です。一方で、不正なメモリアクセスがプログラムのクラッシュや脆弱性につながることがあります。

「メモリのアドレスを直接いじれる」というのは、他の高水準言語にはほぼない特権です。Java や Python では「変数 x には何かが入っている」程度しか意識しませんが、C言語では「変数 x はメモリの 0x7ffd1234 番地にある」という事実に触れられます。コンピュータの内側に手を伸ばせるのが、C言語の魅力のひとつです。

手動メモリ管理

C言語にはガベージコレクション(garbage collection)がありません。プログラマーが malloc() でメモリを確保し、使い終わったら free() で解放する必要があります。JavaやPythonのように「使われなくなったメモリを自動で回収する仕組み」はないため、解放忘れによるメモリリークや、解放後のメモリへのアクセス(use-after-free)などの問題が起きることがあります。

これは「面倒くさい」と「自由」の両面です。GCがある言語は楽ですが、いつ動くか予測できないGCがリアルタイム処理を邪魔することもあります。C言語にはGCがないので、プログラマーが完全に主導権を握れます。組み込みやゲームエンジンのように、1ミリ秒単位のレスポンスが求められる世界では、この主導権が決定的に重要です。

高速な実行速度

C言語のコードはコンパイルによって機械語(machine code)に変換されます。インタプリタを通さずCPU(Central Processing Unit — コンピュータの演算処理を行う中央演算装置)が直接実行できるため、処理速度が非常に速くなります。パフォーマンスが要求される場面でC言語が選ばれる主な理由のひとつです。

「速い」と一口に言っても、Pythonと比較すると数十倍〜数百倍の速度差が出ることがあります。Pythonは可読性に振った言語、C言語はハードウェア性能を絞り出すための言語。用途が違うだけで、優劣の話ではありません。

高い移植性

C言語のソースコードは、コンパイラがある環境であればほとんどのプラットフォームで動かせます。WindowsでもLinuxでも組み込みマイコンでも、同じソースコードをわずかな修正で動作させられます。

たとえば Mac でC言語の小さなツールを書いたとしたら、それを Linux サーバーに送って gcc でコンパイルし直すだけでだいたい動きます。「ソースコードがあれば、どこでも走る」 — これがUNIX/C文化の根っこです。

C言語の長所と短所

C言語の特徴を整理すると、以下のようになります。どちらが「良い・悪い」という話ではなく、用途に対する向き不向きです。

観点長所短所
速度機械語に変換されるため非常に速いコンパイルが必要で、書いてすぐ動かせない
メモリ細かく制御できる手動管理が必要でバグが起きやすい
ハードウェア制御デバイス・OSレベルの操作が可能高水準言語に比べ記述量が増える
移植性規格に準拠すれば多くの環境で動く環境依存の動作(整数サイズなど)がある
型安全性暗黙の型変換が多く、型のミスに気づきにくい
標準ライブラリコアが充実しているJavaやPythonのような豊富な標準ライブラリはない
学習覚えた知識が30年通用するポインタなど概念の習得に時間がかかる

C言語はOSや組み込みなどの低水準領域では今でも第一選択肢のひとつです。一方でWebアプリやスクリプト作業など、速度よりも開発効率が求められる場面では、他の言語の方が向いていることが多いです。

実行の仕組み

C言語のプログラムは「ソースコードを書く → コンパイルする → 実行する」という流れで動きます。Pythonの python3 hello.py やNode.jsの node hello.js のように、コードをそのまま渡して動かすのとは異なります。

コンパイル(compile)とは、人間が書いたソースコードをCPUが直接実行できる機械語(バイナリ)に変換する処理のことです。一度コンパイルすれば、実行時には機械語を CPU が直接処理するため、インタプリタ言語のように都度ソースを解釈する必要がなく高速に動作します。

コンパイルの4段階

ソースコード hello.c プリプロセス #include展開 コンパイル アセンブリ生成 リンク → 実行 バイナリ

実際には gcc コマンド1行でこの全工程が走ります。普段は意識する必要はありませんが、エラーが出たときに「どの段階で失敗したか」を読み取れると、原因の見当がつきやすくなります。

段階処理内容エラーが出たら
プリプロセス#include#define を展開する。コードの変換処理。ヘッダファイルが見つからない、マクロの書き間違い
コンパイルC言語のソースをアセンブリ言語(人間が読める機械語に近い表現)に変換する。構文エラー、型のミスマッチ、未宣言の変数
アセンブルアセンブリ言語をオブジェクトファイル(.o)に変換する。普段見ない(gccがほぼ自動)
リンク複数のオブジェクトファイルやライブラリを結合して、実行可能ファイルを生成する。『undefined reference』 = 関数の定義がない / ライブラリ未指定

printf を使ってるのに『undefined reference to printf』が出る」みたいなエラーは、リンクの段階での失敗です。#include <stdio.h> しただけだと宣言は読み込まれていても、実体(コンパイル済みのバイナリ)はリンク段階で結合されます。標準ライブラリは大体自動でリンクされますが、数学関数の libmgcc -lm が必要だったりします。

最初のプログラムをコンパイルして実行する

hello.c
#include <stdio.h>

int main(void) {
	printf("Hello, World!\n");
	return 0;
}

コンパイルします。

gcc hello.c -o hello

実行します(macOS / Linux の場合)。

./hello
Hello, World!

-o hello は出力ファイルの名前を指定するオプションです。省略すると a.out(Windowsでは a.exe)が生成されます。

Windowsの場合は ./hello の代わりに hello.exe で実行します。

./ は「カレントディレクトリの〜」という意味で、UNIX文化の作法です。これも30年前から変わっていません。

変数と型

C言語は静的型付け言語です。変数を使う前に、その変数がどんな種類のデータを扱うかを型名で宣言する必要があります。int x; と書けば整数専用、double y; と書けば浮動小数点数専用として確定します。

基本の型

主要な型を使ったサンプルです。

#include <stdio.h>

int main(void) {
	int score = 1500; /* 整数 */
	double temperature = 36.5; /* 浮動小数点数(倍精度) */
	char grade = 'A'; /* 文字(1文字) */

	printf("score: %d\n", score);
	printf("temperature: %.1f\n", temperature);
	printf("grade: %c\n", grade);
	return 0;
}
score: 1500
temperature: 36.5
grade: A
用途主なサイズ
int整数4バイト42, -10
char文字 / 小さな整数1バイト'A', 65
double浮動小数点数(倍精度)8バイト3.14
float浮動小数点数(単精度)4バイト3.14f
long大きな整数8バイト(64bit環境)1000000L

型のサイズはプラットフォームによって異なることがあります。同じ int でも、16bit環境では2バイト、32bit/64bit環境では4バイト、というように環境依存の部分が残っています。サイズを厳密に指定したい場合は <stdint.h>int32_tuint64_t を使います。

正確なサイズを調べるには sizeof 演算子を使います。

sizeof 演算子

型のサイズを sizeof で調べるサンプルです。

#include <stdio.h>

int main(void) {
	printf("int: %zu バイト\n", sizeof(int));
	printf("char: %zu バイト\n", sizeof(char));
	printf("double: %zu バイト\n", sizeof(double));
	return 0;
}
int: 4 バイト
char: 1 バイト
double: 8 バイト

sizeof は実行時に呼ばれているように見えて、実はコンパイル時に確定する演算子です。「変数や型がメモリ上で何バイト必要か」をコンパイラが計算して、その値が埋め込まれます。実行時のオーバーヘッドはゼロ。

sizeof の詳細はsizeofで解説しています。

関数

C言語では、処理をまとめるために関数を使います。関数は「戻り値の型 関数名(引数の型 引数名)」という形で定義します。

#include <stdio.h>

/* 2つの整数を足して返す関数 */
int add(int a, int b) {
	return a + b;
}

int main(void) {
	int result = add(1048, 576);
	printf("result: %d\n", result);
	return 0;
}
result: 1624

何も返さない関数は戻り値の型に void を使います。

#include <stdio.h>

void greet(char *name) {
	printf("Hello, %s!\n", name);
}

int main(void) {
	greet("user1");
	greet("user2");
	return 0;
}
Hello, user1!
Hello, user2!

関数は「処理を名前で呼べるようにする」仕組みです。同じコードを2回書く代わりに、関数を1つ用意して2回呼べば、修正があっても1箇所直すだけで済みます。プログラミングの基本中の基本でありながら、関数を使いこなせるかどうかでコードの保守性が劇的に変わります。

関数の定義・プロトタイプ宣言の詳細は関数の定義で解説しています。

制御構文

if / else if / else

条件分岐の基本パターンです。

#include <stdio.h>

int main(void) {
	int level = 120;

	if (level < 80) {
		printf("level_a\n");
	} else if (level < 100) {
		printf("level_b\n");
	} else {
		printf("level_c\n");
	}
	return 0;
}
level_c

if の詳細はif / else / else ifで解説しています。

for ループ

指定した回数だけ処理を繰り返すループです。

#include <stdio.h>

int main(void) {
	int i;
	for (i = 1; i <= 3; i++) {
		printf("ループ %d 回目\n", i);
	}
	return 0;
}
ループ 1 回目
ループ 2 回目
ループ 3 回目

while ループ

条件が真の間、処理を繰り返すループです。

#include <stdio.h>

int main(void) {
	int count = 0;
	while (count < 3) {
		printf("count: %d\n", count);
		count++;
	}
	return 0;
}
count: 0
count: 1
count: 2

switch / case

値に応じて処理を分岐する構文です。

#include <stdio.h>

int main(void) {
	int day = 3;

	switch (day) {
		case 1:
			printf("月曜日\n");
			break;
		case 2:
			printf("火曜日\n");
			break;
		case 3:
			printf("水曜日\n");
			break;
		default:
			printf("その他\n");
			break;
	}
	return 0;
}
水曜日

break を忘れると次の case に流れ込んで(フォールスルー)動作します。これは意図的に使うこともありますが、書き忘れによるバグの原因にもなります。

for / while の詳細はfor / while / do-whileで、switch の詳細はswitch / caseで解説しています。

配列

配列は同じ型のデータを連続して並べたものです。インデックス(添字)は0から始まります。

#include <stdio.h>

int main(void) {
	int scores[3] = {1500, 1200, 1800};
	int i;

	for (i = 0; i < 3; i++) {
		printf("scores[%d] = %d\n", i, scores[i]);
	}
	return 0;
}
scores[0] = 1500
scores[1] = 1200
scores[2] = 1800

C言語の配列には「範囲外アクセスのチェック機能がない」という特徴があります。配列の範囲を超えたインデックスにアクセスしても、コンパイルエラーにはならず、実行時に未定義の動作を引き起こします。scores[100] と書いても、コンパイラはエラーを出さずメモリの該当アドレスにアクセスする機械語を出力します。その領域に何が入っているかは保証されず、実行時の動作は環境依存となります。添字の範囲管理はプログラマーが行う必要があります。

これは Trust the programmer(プログラマーを信頼する) という C 言語の設計原則のひとつで、ANSI C / ISO C の精神として明文化されています。言語側で過剰な制約を課さず、安全性の管理はプログラマーが責任を持つという方針です。柔軟性が高い反面、配列範囲チェックや型チェックなどはプログラマー側で実装する必要があります。

範囲外アクセスは C 言語仕様で未定義動作(Undefined Behavior、略して UB)と定められています。UB は「何が起きても仕様違反ではない」という意味で、正常に動くこともあればクラッシュすることもあり、全く無関係な変数が壊れることもあります。コンパイラは UB が起きない前提で最適化を行うため、UB を含むコードは最適化によって意図しない動作に変わることがあります。C 言語を書くうえで UB を避けることは最も重要な原則のひとつです。

配列の詳細は配列で解説しています。

ポインタ — C言語の心臓

ポインタとは何か

ポインタはC言語最大の特徴のひとつで、変数のメモリアドレスを格納する変数です。* でポインタ型を宣言し、&(アドレス演算子)で変数のアドレスを取得します。

#include <stdio.h>

int main(void) {
	int x = 42;
	int *p = &x; /* p は x のアドレスを保持するポインタ */

	printf("x の値: %d\n", x);
	printf("x のアドレス: %p\n", (void *)&x);
	printf("p が指す値 (*p): %d\n", *p);

	*p = 100; /* ポインタ経由で x の値を変更する */
	printf("変更後の x: %d\n", x);
	return 0;
}
x の値: 42
x のアドレス: 0x7ffd5e8a3b2c(実行環境により異なる)
p が指す値 (*p): 42
変更後の x: 100

ポインタの基本動作 — メモリアドレスと値の対応

ポインタの動作は、配列とインデックスの関係に近い構造を持っています。配列『a』のインデックス『i』に対して『a[i]』が「インデックスが指す位置にある値」を返すように、ポインタ『p』(メモリアドレスを保持する変数)に対して『*p』が「アドレスが指す位置にある値」を返します。実際 C 言語仕様では『*p』と『p[0]』は同義です。

コード動作
int x = 42;メモリ上のある位置に整数『42』が格納される
int *p = &x;その位置のメモリアドレスをポインタ『p』に代入する
*pポインタ『p』が保持するアドレスにある値(『42』)を取り出す
*p = 100;ポインタ『p』が保持するアドレス位置に値『100』を書き込む

ポインタはアドレス値(通常 64bit 環境で 8 バイト)の整数です。100MB の巨大なデータでも、ポインタ(8 バイト)を関数に渡すだけで参照が可能で、データ本体をコピーする必要がありません。これがポインタが効率的な理由です。

ポインタで参照渡し

ポインタを使うと関数に変数を「参照渡し」することができます。C言語は引数を値渡しするため、ポインタなしで関数内から呼び出し元の変数を変更することはできません。

#include <stdio.h>

/* ポインタを受け取って値を2倍にする関数 */
void doubleValue(int *n) {
	*n = *n * 2;
}

int main(void) {
	int hp = 100;
	doubleValue(&hp);
	printf("hp: %d\n", hp);
	return 0;
}
hp: 200

『&hp』で『hp』のメモリアドレスを引数として渡し、関数内では『*n』としてそのアドレスにある値を書き換えています。アドレス値が引数として渡っているため、関数側から呼び出し元の変数『hp』に直接アクセスして書き換えが可能です。

ポインタが分かると見える世界

ポインタを理解すると、これまで見えなかった他の言語の挙動が腑に落ちます:

  • JavaScriptで配列を関数に渡すと中身が書き換わる → 中身は実は参照(ポインタ的なもの)
  • Pythonで list を関数に渡すと中身が書き換わる → これも参照
  • Javaのオブジェクトが「null」のときにエラーが出る → NULLポインタの参照
  • Goの *& がよく分からない → そっくりそのままC言語

ポインタはコンピュータがメモリを扱う仕組みをそのまま言語の構文に落とし込んだ機能であり、C 言語の設計思想の中核を成します。ポインタの理解は、C 言語のメモリモデル全体の理解に直結します。

ポインタの基本はポインタ基本で、配列とポインタの関係はポインタと配列で解説しています。

文字列(char配列)

C言語には文字列型がありません。文字列はchar 型の配列で表現します。末尾にヌル文字 '\0' を置くことで文字列の終わりを示す仕組みになっています。

#include <stdio.h>
#include <string.h>

int main(void) {
	char name[] = "sample_text";

	printf("名前: %s\n", name);
	printf("文字数: %zu\n", strlen(name));
	return 0;
}
名前: sample_text
文字数: 11

『sample_text』という11文字の文字列ですが、メモリ上では12バイト確保されています。最後の1バイトに '\0'(ヌル文字)が入っていて、これが「文字列の終わり」を示しています。strlen() は先頭から '\0' までの文字数を数えるので、ヌル文字を含まない11を返します。

JavaやPythonの「文字列クラス」ではなく、ただの char 配列。これがC言語の素朴さです。

strlen() は文字列の長さを返す標準ライブラリ関数で、<string.h> をインクルードすることで使えます。

文字列操作関数(strlenstrcpystrncpy)の詳細はstrlen / strcpy / strncpyで解説しています。

構造体

構造体(struct)は、複数の変数をひとまとめにしたデータ型です。関連するデータを1つの単位として扱えるようになります。「クラス」のないC言語で、データをまとめる主な手段です。

#include <stdio.h>

struct Member {
	char name[32];
	int age;
	char dept[64];
};

int main(void) {
	struct Member m1;

	/* フィールドに値を設定する */
	snprintf(m1.name, sizeof(m1.name), "user1");
	m1.age = 25;
	snprintf(m1.dept, sizeof(m1.dept), "engineering");

	printf("名前: %s\n", m1.name);
	printf("年齢: %d\n", m1.age);
	printf("所属: %s\n", m1.dept);
	return 0;
}
名前: user1
年齢: 25
所属: engineering

typedef を使うと struct キーワードを省略して型名だけで宣言できます。

typedef struct {
	char name[32];
	int age;
} Member;

Member m2; /* struct を書かなくてよい */

「クラスがないと不便そう」と思われがちですが、構造体 + 関数で大半のことはこなせます。Linuxカーネルもブラウザの中身も、ほとんどが構造体と関数の組み合わせで動いています。クラスがなくても巨大なシステムは作れる、という事実は心強いです。

構造体の詳細はstruct(構造体)で、typedef の詳細はtypedefで解説しています。

ファイルI/O(Input/Output:入出力)

C言語でファイルを読み書きするには FILE 型のポインタと、<stdio.h> に含まれる関数を使います。

#include <stdio.h>

int main(void) {
	FILE *fp;
	char buf[128];

	/* ファイルを開く(書き込みモード) */
	fp = fopen("message.txt", "w");
	if (fp == NULL) {
		printf("ファイルを開けませんでした\n");
		return 1;
	}
	fprintf(fp, "sample message\n");
	fclose(fp);

	/* ファイルを開く(読み込みモード) */
	fp = fopen("message.txt", "r");
	if (fp == NULL) {
		printf("ファイルを開けませんでした\n");
		return 1;
	}
	fgets(buf, sizeof(buf), fp);
	fclose(fp);

	printf("読み込んだ内容: %s", buf);
	return 0;
}
読み込んだ内容: sample message

fopen() で開いたファイルは必ず fclose() で閉じる必要があります。閉じないと OS が割り当てたファイルディスクリプタ(OS が管理するファイルへのハンドル)が枯渇したり、書き込みがバッファに残ったまま消えたりすることがあります。「開けたら閉じる」はC言語の鉄則です。

fopen() の詳細はfopen / fcloseで解説しています。

メモリレイアウト — スタックとヒープ

C言語を使っていると、必ず「スタック」「ヒープ」という言葉に出会います。これはプログラム実行中にメモリがどう使われているかの話で、ポインタやmallocを正しく使うために避けて通れない概念です。

プログラムが使うメモリの区画

メモリ テキスト領域(実行コード) データ領域(グローバル変数) ヒープ(malloc で確保) ↓ 上に伸びる スタック(関数のローカル変数) ↑ 下に伸びる 高アドレス 低アドレス 関数間で生き残る 関数を抜けると消える

プログラムが起動すると、OS はメモリ上にいくつかの区画を用意します。重要なのは下の2つ:

  • スタック: 関数のローカル変数が置かれる場所。関数が呼ばれるたびに領域が確保され、関数を抜けると自動で破棄される。速い、サイズ制限あり、自動管理。
  • ヒープ: malloc() で確保するメモリ領域。プログラマーが free() するまで生き続ける。サイズの自由度が高い、手動管理が必要。

スタックの例

関数内のローカル変数はスタックに確保されます。

#include <stdio.h>

void greet(void) {
	int local_score = 1500; /* スタックに確保される */
	printf("%d\n", local_score);
} /* 関数を抜けるとスタックから消える */

int main(void) {
	greet();
	return 0;
}
1500

関数 greet の中で宣言した local_score はスタック上に置かれ、関数が終わると自動で消えます。free() を書く必要はありません。これがスタックの良いところ。

ヒープの例

malloc() で確保したメモリはヒープに置かれ、関数を抜けても残ります。

#include <stdio.h>
#include <stdlib.h>

int *create_score(void) {
	int *p = (int *)malloc(sizeof(int)); /* ヒープに確保 */
	*p = 1500;
	return p; /* 関数を抜けても残る */
}

int main(void) {
	int *score = create_score();
	printf("%d\n", *score);
	free(score); /* 使い終わったら自分で解放 */
	return 0;
}
1500

ヒープに確保したメモリは関数を抜けても残ります。これが「関数間でデータを受け渡す」「実行時に必要なサイズを決める」場面で必須になります。一方で free() し忘れると、プログラムが終了するまでメモリが居座り続けます(メモリリーク)。

スタックは「自動」、ヒープは「手動」。便利と引き換えに責任を負うのがC言語スタイルです。

メモリ管理(malloc / free)

C言語では、プログラム実行中に必要なサイズが決まるメモリを動的に確保することができます。malloc()(memory allocation)で確保し、使い終わったら free() で解放します。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
	int n = 3;
	int *scores;
	int i;

	/* int 型 n 個分のメモリを確保する */
	scores = (int *)malloc(sizeof(int) * n);
	if (scores == NULL) {
		printf("メモリ確保に失敗しました\n");
		return 1;
	}

	scores[0] = 1500;
	scores[1] = 1200;
	scores[2] = 1800;

	for (i = 0; i < n; i++) {
		printf("scores[%d] = %d\n", i, scores[i]);
	}

	/* 使い終わったら必ず解放する */
	free(scores);
	return 0;
}
scores[0] = 1500
scores[1] = 1200
scores[2] = 1800

malloc() で確保したメモリは free() で解放しないと、プログラム終了まで解放されません(メモリリーク)。また、free() 後のポインタに再アクセスするのも未定義の動作を引き起こします。

手間はかかりますが、これはプログラマーが完全にメモリを支配できるということでもあります。GC が勝手に動いて処理を止めることもなければ、確保のタイミングも解放のタイミングも自分で決められます。リアルタイム性が必要な場面では、この主導権が決定的に重要です。

malloc / free の詳細はmalloc / free / calloc / reallocで解説しています。

プリプロセッサ

プリプロセッサ(preprocessor)は、コンパイルの前にソースコードを変換する仕組みです。#include#define などの # で始まる命令がプリプロセッサディレクティブ(directive)です。

#include — ファイルの取り込み

#include は指定したファイルの内容をその場に展開します。標準ライブラリのヘッダファイルは <> で、自分で作ったファイルは "" で指定します。

#include <stdio.h> /* 標準ライブラリ(printf など) */
#include <stdlib.h> /* malloc / free など */
#include <string.h> /* strlen / strcpy など */
#include "myheader.h" /* 自分で作ったヘッダファイル */

#include <stdio.h>』とすると、コンパイラはそのファイルの中身(関数の宣言など)をその場にコピペします。これは物理的にコピペが起きているので、includeしたファイルの行数が増えれば、その分コンパイル時間も増えます。

#define — マクロ定義

#define はコンパイル前に文字列の置換を行います。定数や短い処理の置換に使われます。

#include <stdio.h>

#define MAX_HP 9999
#define SQUARE(x) ((x) * (x))

int main(void) {
	printf("MAX_HP: %d\n", MAX_HP);
	printf("SQUARE(5): %d\n", SQUARE(5));
	return 0;
}
MAX_HP: 9999
SQUARE(5): 25

#define は単なる文字列置換なので、コンパイラは型をチェックしません。便利ですが、思わぬバグの温床にもなります。最近は constinline 関数で代用することが多いです。

#include / #define の詳細は#include / #defineで解説しています。

よくあるエラーと解決方法

コンパイルエラー: implicit declaration of function

関数を使う前に宣言がない場合に発生します。必要なヘッダファイルを #include しているか確認してください。

/* NG: stdio.h のインクルード忘れ */
int main(void) {
	printf("Hello\n"); /* warning: implicit declaration of function 'printf' */
	return 0;
}

同じ処理を次のようにも書けます。

/* OK: 先頭で stdio.h をインクルードする */
#include <stdio.h>

int main(void) {
	printf("Hello\n");
	return 0;
}
Hello

セグメンテーション違反(Segmentation fault)

アクセスしてはいけないメモリ領域に触れた場合に発生します。主な原因は、ヌルポインタの参照、配列の範囲外アクセス、free() 後のポインタ参照などです。

int *p = NULL;
*p = 42; /* NG: NULL ポインタへの書き込み → Segmentation fault */

プログラムが突然終了する典型的なエラーです。原因はほぼ100%ポインタ周りで、NULLポインタへのアクセス、配列範囲外アクセス、free() 済みポインタの参照のいずれかに集約されます。

無限ループ

for / while のループ変数の更新忘れや、条件が永遠に真になるときに発生します。

int i = 0;
while (i < 5) {
	printf("%d\n", i);
	/* NG: i++ を忘れると無限ループ */
}

未初期化変数の使用

C言語のローカル変数は宣言しただけでは初期化されません。何が入っているか分からない「ゴミ値」を持つ場合があります。

int x;
printf("%d\n", x); /* NG: 未初期化。何が出るか分からない */

同じ処理を次のようにも書けます。

int x = 0;
printf("%d\n", x); /* OK: 初期値を設定する */

メモリリーク

malloc() で確保したメモリを free() せずに放置すると、プログラム終了までメモリが解放されません。短時間動くプログラムでは気づきにくいですが、サーバーや常駐プロセスでは致命的です。

int *p = (int *)malloc(sizeof(int) * 100);
/* ... 何か処理する ... */
return 0; /* NG: free(p) を忘れている */

同じ処理を次のようにも書けます。

int *p = (int *)malloc(sizeof(int) * 100);
/* ... 何か処理する ... */
free(p); /* OK: 使い終わったら必ず解放 */
return 0;

コメントの書き方

C言語でコメントを書くための記法です。コメントはコードの動作には影響せず、コードの意図を伝えたり、一時的に処理を無効化(コメントアウト)したりするために使います。

コメントの種類

種類書き方概要
ブロックコメント/* テキスト */『/*』から『*/』までの範囲がコメントになります。複数行にわたるコメントを書くときに使います。C言語の伝統的なコメント記法です。
一行コメント// テキスト『//』から行末までがコメントになります。C99で正式に追加されました。C++から取り入れられた記法です。

サンプル

一行コメントとブロックコメントの両方を使ったサンプルです。

#include <stdio.h>

/* calculate_area: 幅と高さから面積を返す */
int calculate_area(int width, int height) {
    return width * height;
}

int main(void) {
    int w = 10;
    int h = 5;
    int area = calculate_area(w, h); // 面積を計算

    /*
     * printf で結果を出力する。
     * %d は int 型のプレースホルダ。
     */
    printf("area = %d\n", area);
    return 0;
}
area = 50

コメントの内容は自由です。主な使い道としては「なぜこう書いたか」「何をしているか」「TODO」「メモ」などが挙げられますが、必要だと思った内容を自由に記載してしまって問題ありません。コメント記法の詳細や各パターンについてはコメント解説ページで詳しく解説しています。

おすすめ学習順

C言語の機能は多岐にわたりますが、以下の順番で学ぶと効率よく理解を積み上げられます。

ステップ習得内容対応ページ
1 環境構築・最初のプログラム 環境構築

main関数
2 変数・型 int / char / double / float
3 制御フロー if / else / else if

for / while / do-while
4 関数・配列 関数の定義

配列
5 ポインタ(C言語の心臓) ポインタ基本

ポインタと配列
6 文字列操作 strlen / strcpy / strncpy
7 構造体・メモリ管理 struct(構造体)

malloc / free
8 プリプロセッサ・ファイル操作 #include / #define

fopen / fclose

ポインタ(ステップ5)はC言語の中でも特に理解に時間がかかる箇所です。「アドレスとは何か」「間接参照とは何か」をしっかり押さえてから次に進むことが重要です。

概要

C言語は1972年にデニス・リッチー様がUNIXのために開発した低水準寄りのプログラミング言語です。OSや組み込みシステムから、デバイスドライバ、パフォーマンスが要求されるアプリケーションまで、幅広い用途で現在も使われています。

コードはコンパイルによって機械語に変換されるため、実行速度が非常に速いという特徴があります。一方で、ポインタによる手動メモリ管理、型変換の暗黙的な動作など、丁寧に扱わないとバグの原因になる仕組みも多く持っています。

C言語を学ぶことでコンピュータの動作原理(メモリ・アドレス・スタック・ヒープなど)を直接扱うことになるため、OSカーネル・組み込みシステム・デバイスドライバの開発で長年使われています。さらに、C++・Java・C#・JavaScript・PHP・Python など現代の主要言語の文法のベースにもなっており、「C言語を学ぶことは、ほぼ全ての言語の地盤を学ぶこと」でもあります。

規格は C89 から始まり C99、C11、C17、C23 と改訂が続いており、2023年に制定された C23 が現時点の最新規格です。改訂の間隔が長く、一度覚えた知識が何十年も通用する点もC言語の魅力のひとつです。

このサイトの辞典では、各機能を実例とともに解説しています。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。