よく使うC言語の構文や関数を目的別にまとめたクイックリファレンス
1. 基本入出力 (stdio.h) ⌨️
コンソールへの出力や、コンソールからの入力を扱う基本的な関数群です。
目的 | 関数/書式 | 説明 | コード例 |
---|---|---|---|
標準出力への書式付き出力 | printf() |
指定した書式に従って、文字列や変数の値を標準出力(通常は画面)に出力します。改行は \n を含める必要があります。 |
|
標準入力からの書式付き入力 | scanf() |
指定した書式に従って、標準入力(通常はキーボード)からデータを受け取り、変数に格納します。アドレス演算子 & が必要です。バッファオーバーフローに注意が必要です。 |
|
1文字を標準出力へ出力 | putchar() |
引数で指定された1文字を標準出力に出力します。 |
|
1文字を標準入力から入力 | getchar() |
標準入力から1文字を読み込み、その文字のASCIIコードを返します。入力がない場合やエラー時はEOFを返します。 |
|
文字列を標準出力へ出力 | puts() |
引数で指定された文字列を標準出力に出力し、自動的に改行を追加します。 |
|
文字列を標準入力から入力 | fgets() |
標準入力から指定した最大文字数までの文字列(改行文字を含む)を読み込み、指定した文字配列に格納します。gets() より安全です。 |
|
標準エラー出力への書式付き出力 | fprintf(stderr, ...) |
printf と同様の書式で、標準エラー出力 (stderr) へ出力します。エラーメッセージの表示によく使われます。 |
|
2. データ型と変数操作 🔢
C言語で使用できる基本的なデータ型と、変数に関連する操作です。
基本データ型
型名 | 説明 | サイズ例 (環境依存) | 値の範囲例 (環境依存) |
---|---|---|---|
char |
文字または小さな整数 | 1バイト | -128 ~ 127 または 0 ~ 255 |
int |
整数 | 2バイト or 4バイト | -32,768 ~ 32,767 または -2,147,483,648 ~ 2,147,483,647 |
short |
短精度整数 (short int ) |
2バイト | -32,768 ~ 32,767 |
long |
長精度整数 (long int ) |
4バイト or 8バイト | -2,147,483,648 ~ 2,147,483,647 またはそれ以上 |
long long |
さらに長精度の整数 (C99以降) | 8バイト | -(2^63) ~ (2^63)-1 |
float |
単精度浮動小数点数 | 4バイト | 約 ±3.4e +/- 38 (有効桁数6桁程度) |
double |
倍精度浮動小数点数 | 8バイト | 約 ±1.7e +/- 308 (有効桁数15桁程度) |
long double |
拡張倍精度浮動小数点数 | 10バイト, 12バイト, 16バイトなど | double より高精度・広範囲 |
void |
型がないことを示す (関数の戻り値、ポインタなど) | N/A | N/A |
修飾子
signed
: 符号付き (デフォルトで省略されることが多い)unsigned
: 符号なし (非負の整数のみを扱う)const
: 定数 (値を変更できない変数)volatile
: 最適化を抑制 (変数値が予期せず変更される可能性がある場合)static
: 静的変数 (関数内: 寿命がプログラム終了まで / ファイルスコープ: 外部参照不可)extern
: 外部変数 (他のファイルで定義されたグローバル変数を参照)register
: レジスタ変数 (高速アクセスを試みるが、コンパイラ判断)typedef
: 既存の型に別名をつける
typedef unsigned long long ull; // unsigned long long に ull という別名
ull large_number = 123456789012345ULL;
const float PI = 3.14159f;
// PI = 3.14; // エラー: const変数は変更不可
static int counter = 0; // 静的変数
型変換 (キャスト)
あるデータ型の値を別のデータ型に変換します。
- 暗黙的型変換: コンパイラが自動で行う (例:
int
をdouble
に代入) - 明示的型変換 (キャスト): プログラマが型を指定して変換
int i = 10;
double d = 3.14;
double result_implicit = i + d; // iがdoubleに暗黙的に変換される (10.0 + 3.14)
int i_explicit = (int)d; // dをintに明示的にキャスト (小数点以下切り捨て) -> i_explicitは3になる
float f = 5.0f / 2; // 暗黙変換で 5.0f / 2.0f となり、結果は2.5f
int j = 5 / 2; // 整数同士の除算なので、結果は2
float f_cast = (float)5 / 2; // 5をfloatにキャスト -> 5.0f / 2 -> 結果は2.5f
3. 演算子 ✨
計算や比較、論理演算などを行うための記号です。
算術演算子
+
(加算), -
(減算), *
(乗算), /
(除算), %
(剰余)
int a = 10, b = 3;
int sum = a + b; // 13
int diff = a - b; // 7
int prod = a * b; // 30
int quot = a / b; // 3 (整数除算)
int rem = a % b; // 1 (剰余)
代入演算子
=
(代入), +=
, -=
, *=
, /=
, %=
(複合代入)
int x = 5;
x += 3; // x = x + 3; -> xは8
x *= 2; // x = x * 2; -> xは16
インクリメント/デクリメント演算子
++
(インクリメント), --
(デクリメント)
- 前置 (
++x
,--x
): 値を変更してから式で使用 - 後置 (
x++
,x--
): 式で使用してから値を変更
int count = 0;
printf("%d\n", ++count); // 1 (countを1にしてから出力)
printf("%d\n", count++); // 1 (countを出力してから2にする)
printf("%d\n", count); // 2
比較演算子
==
(等しい), !=
(等しくない), >
(より大きい), <
(より小さい), >=
(以上), <=
(以下)
結果は真 (通常 1) または偽 (0) となります。
int p = 5, q = 10;
if (p < q) { // 真 (1)
printf("pはqより小さい\n");
}
if (p == 5) { // 真 (1)
printf("pは5です\n");
}
論理演算子
&&
(論理AND), ||
(論理OR), !
(論理NOT)
短絡評価(ショートサーキット評価)が行われます。
int age = 20;
int score = 85;
if (age >= 20 && score >= 80) { // 真 && 真 -> 真
printf("合格\n");
}
if (age < 18 || score > 90) { // 偽 || 偽 -> 偽
printf("特別条件\n");
}
if (!(age < 18)) { // !(偽) -> 真
printf("18歳以上\n");
}
ビット演算子
&
(ビットAND), |
(ビットOR), ^
(ビットXOR), ~
(ビットNOT), <<
(左シフト), >>
(右シフト)
unsigned char flags = 0b00001010; // 10 (十進数)
unsigned char mask = 0b00000100; // 4 (十進数)
if (flags & mask) { // 0b00000000 -> 偽 (0)
printf("フラグ4が立っています\n");
}
flags |= 0b00000011; // flags = 0b00001011 (ORでフラグを立てる)
flags &= ~mask; // flags = 0b00001011 (ANDとNOTでフラグを消す)
int shifted = 5 << 2; // 0b101 << 2 -> 0b10100 (20) 右に0を追加
int r_shifted = 20 >> 1; // 0b10100 >> 1 -> 0b1010 (10) 右端を削除 (算術/論理シフト注意)
条件演算子 (三項演算子)
条件式 ? 真の場合の値 : 偽の場合の値
int a = 10, b = 20;
int max_val = (a > b) ? a : b; // a > b が偽なので b (20) が max_val に代入される
printf("最大値: %d\n", max_val);
sizeof 演算子
データ型や変数のサイズをバイト単位で返します。
printf("intのサイズ: %zu バイト\n", sizeof(int));
double arr[10];
printf("double配列arrのサイズ: %zu バイト\n", sizeof(arr)); // 配列全体のサイズ
printf("double配列arrの要素数: %zu\n", sizeof(arr) / sizeof(arr[0])); // 要素数
ポインタ関連演算子
&
(アドレス演算子), *
(間接参照演算子)
ポインタセクションで詳しく説明します。
演算子の優先順位と結合規則
複雑な式では、演算子の優先順位と結合規則(左から右、右から左など)が重要になります。不明瞭な場合は括弧 ()
を使用して明示的に順序を指定することが推奨されます。
int result = 5 + 3 * 2; // 乗算が優先 -> 5 + 6 = 11
int result2 = (5 + 3) * 2; // 括弧内が優先 -> 8 * 2 = 16
int x, y, z;
x = y = z = 0; // 代入演算子は右結合 (z=0, y=z, x=y)
4. 制御フロー 🧭
プログラムの実行順序を制御するための構文です。
条件分岐 (if-else)
int score = 75;
if (score >= 90) {
printf("優\n");
} else if (score >= 70) {
printf("良\n"); // このブロックが実行される
} else if (score >= 50) {
printf("可\n");
} else {
printf("不可\n");
}
条件分岐 (switch)
整数型または文字型の式の結果に基づいて処理を分岐します。case
ラベルと break
文の組み合わせが一般的です。
char grade = 'B';
switch (grade) {
case 'A':
printf("素晴らしい!\n");
break; // switch文を抜ける
case 'B':
printf("良い成績です。\n");
// breakがない場合、次のcaseも実行される (フォールスルー)
case 'C':
printf("もう少し頑張りましょう。\n");
break;
case 'D':
printf("再試が必要です。\n");
break;
default: // どのcaseにも一致しない場合
printf("評価不能です。\n");
break;
}
繰り返し (while)
条件式が真の間、ブロック内の処理を繰り返します。最初に条件を評価します。
int i = 0;
while (i < 5) {
printf("i = %d\n", i);
i++; // 条件を変える処理を忘れないように
}
// 出力: i = 0, i = 1, i = 2, i = 3, i = 4
繰り返し (do-while)
ブロック内の処理を最低1回実行し、その後、条件式が真の間繰り返します。最後に条件を評価します。
int count = 0;
char choice;
do {
printf("カウント: %d\n", count);
printf("続けますか? (y/n): ");
// scanf(" %c", &choice); // 先頭の空白で前の入力の改行を読み飛ばす
choice = getchar(); // 1文字読み込み
while(getchar() != '\n'); // バッファに残った改行などを読み捨てる
count++;
} while (choice == 'y' || choice == 'Y');
繰り返し (for)
指定された回数や範囲で処理を繰り返すのに適しています。初期化式、条件式、変化式を持ちます。
// 0から9まで10回繰り返す
for (int j = 0; j < 10; j++) {
printf("j = %d\n", j);
}
// 複数の初期化・変化式も可能 (カンマ区切り)
for (int x = 0, y = 5; x < y; x++, y--) {
printf("x=%d, y=%d\n", x, y);
}
// 出力: x=0, y=5 / x=1, y=4 / x=2, y=3
ループ制御
break
: 現在実行中のループ (while
,do-while
,for
) またはswitch
文を強制的に終了します。continue
: 現在のループのイテレーションをスキップし、次のイテレーション(条件評価または変化式)に進みます。
for (int k = 0; k < 10; k++) {
if (k == 3) {
continue; // kが3の時は以下のprintfをスキップし、次のk=4へ
}
if (k == 7) {
break; // kが7になったらループを抜ける
}
printf("k = %d\n", k);
}
// 出力: k = 0, k = 1, k = 2, k = 4, k = 5, k = 6
ジャンプ (goto)
プログラム内の指定されたラベル位置に無条件にジャンプします。コードが複雑になりやすいため、通常は使用を避けるべきですが、多重ループからの脱出などで限定的に使われることがあります。
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (i * j > 10) {
printf("条件成立 (%d, %d)\n", i, j);
goto loop_exit; // ループの外のラベルへジャンプ
}
}
}
loop_exit: // ラベル定義
printf("ループ処理終了\n");
5. 関数 🧱
特定の処理をまとめて名前を付け、再利用可能にする仕組みです。
関数の定義
// 戻り値の型 関数名(引数リスト) {
// 処理;
// return 戻り値; // 戻り値がない (void)場合は不要
// }
// 2つの整数の和を計算する関数
int add(int a, int b) {
int sum = a + b;
return sum;
}
// 何も返さない (副作用が目的の) 関数
void print_message(const char* message) {
printf("%s\n", message);
}
// 引数を取らない関数
int get_random_number() {
// 実際には乱数生成処理 (stdlib.h の rand() など)
return 42; // 仮
}
関数の呼び出し
int main() {
int result = add(5, 3); // add関数を呼び出し、戻り値を受け取る
printf("5 + 3 = %d\n", result); // 出力: 8
print_message("Hello from function!"); // print_message関数を呼び出す
int random_num = get_random_number();
printf("Random number: %d\n", random_num);
return 0;
}
関数プロトタイプ宣言
関数を定義する前(または別のファイルで定義されている場合)に呼び出す場合、コンパイラに関数のシグネチャ(戻り値の型、関数名、引数の型リスト)を知らせる必要があります。
#include <stdio.h>
// 関数プロトタイプ宣言
int add(int a, int b);
void print_message(const char* message);
int main() {
// プロトタイプがあるので、mainより後で定義されていても呼び出せる
int result = add(10, 20);
print_message("Function prototype example");
printf("Result: %d\n", result);
return 0;
}
// 関数の実際の定義
int add(int a, int b) {
return a + b;
}
void print_message(const char* message) {
printf("%s\n", message);
}
引数の渡し方
- 値渡し (Pass by Value): 関数の仮引数には、実引数の値のコピーが渡されます。関数内で仮引数を変更しても、呼び出し元の実引数には影響しません。C言語のデフォルトです。
- 参照渡し (Pass by Reference) (ポインタを使用): 引数のアドレスを渡します。関数内でポインタを介して値を変更すると、呼び出し元の変数の値も変更されます。
#include <stdio.h>
// 値渡しの例
void modify_value(int val) {
val = 100; // この変更は呼び出し元に影響しない
printf("Inside modify_value: val = %d\n", val);
}
// 参照渡し(ポインタ)の例
void modify_value_by_ref(int* ptr) {
*ptr = 200; // ポインタ経由で値を変更すると呼び出し元も変わる
printf("Inside modify_value_by_ref: *ptr = %d\n", *ptr);
}
int main() {
int x = 10;
printf("Before modify_value: x = %d\n", x);
modify_value(x);
printf("After modify_value: x = %d\n", x); // xは10のまま
int y = 50;
printf("Before modify_value_by_ref: y = %d\n", y);
modify_value_by_ref(&y); // yのアドレスを渡す
printf("After modify_value_by_ref: y = %d\n", y); // yは200に変わる
return 0;
}
再帰関数 (Recursive Function)
関数が自分自身を呼び出すことです。階乗の計算や木構造の探索などで使われます。終了条件(ベースケース)が必須です。
// 階乗を計算する再帰関数
long long factorial(int n) {
if (n < 0) {
return -1; // エラーケース
}
// ベースケース (終了条件)
if (n == 0 || n == 1) {
return 1;
}
// 再帰ステップ
return n * factorial(n - 1);
}
int main() {
printf("5! = %lld\n", factorial(5)); // 120
return 0;
}
main関数
プログラムの実行が開始される特別な関数です。
int main(void)
: 引数を取らない形式。int main(int argc, char *argv[])
: コマンドライン引数を受け取る形式。argc
: コマンドライン引数の数(プログラム名を含む)。argv
: コマンドライン引数の文字列を格納したポインタ配列 (argv[0]
はプログラム名)。
- 戻り値
int
: プログラムの終了ステータスを示します。通常、正常終了は0
、異常終了は0
以外(例:1
,EXIT_FAILURE
)を返します。
#include <stdio.h>
#include <stdlib.h> // EXIT_SUCCESS, EXIT_FAILURE のため
int main(int argc, char *argv[]) {
printf("プログラム名: %s\n", argv[0]);
printf("コマンドライン引数の数: %d\n", argc);
if (argc > 1) {
printf("与えられた引数:\n");
for (int i = 1; i < argc; i++) {
printf(" argv[%d]: %s\n", i, argv[i]);
}
} else {
printf("コマンドライン引数はありません。\n");
}
return EXIT_SUCCESS; // return 0; と同じ意味
}
6. 配列 📊
同じ型のデータを連続したメモリ領域に格納するデータ構造です。
1次元配列
- 宣言:
データ型 配列名[要素数];
- 初期化:
データ型 配列名[要素数] = {値1, 値2, ...};
(要素数を省略可能) - アクセス:
配列名[インデックス]
(インデックスは0から始まる)
int scores[5]; // 要素数5のint型配列を宣言 (値は不定)
scores[0] = 80;
scores[1] = 95;
// ... scores[4] まで
double values[] = {1.1, 2.2, 3.3, 4.4}; // 初期化子から要素数4が決定される
// 配列要素の走査
for (int i = 0; i < 4; i++) {
printf("values[%d] = %.1f\n", i, values[i]);
}
// 配列サイズと要素数の計算
int numbers[] = {10, 20, 30, 40, 50, 60};
size_t array_size = sizeof(numbers); // 配列全体のバイトサイズ
size_t element_size = sizeof(numbers[0]); // 1要素のバイトサイズ
size_t num_elements = array_size / element_size; // 要素数
printf("要素数: %zu\n", num_elements); // 6
多次元配列
配列の配列として表現されます。主に2次元配列(行列)がよく使われます。
- 宣言:
データ型 配列名[行数][列数];
- 初期化:
データ型 配列名[行数][列数] = {{行1値1, 行1値2,...}, {行2値1, 行2値2,...}, ...};
- アクセス:
配列名[行インデックス][列インデックス]
int matrix[3][4]; // 3行4列のint型2次元配列
int table[2][3] = {
{1, 2, 3}, // 1行目 (table[0])
{4, 5, 6} // 2行目 (table[1])
};
// 要素へのアクセス
table[1][1] = 55; // 2行目、2列目の要素を55に変更
// 2次元配列の走査
for (int i = 0; i < 2; i++) { // 行ループ
for (int j = 0; j < 3; j++) { // 列ループ
printf("table[%d][%d] = %d\t", i, j, table[i][j]);
}
printf("\n"); // 各行の終わりで改行
}
配列と関数
配列を関数に渡す場合、通常は配列の先頭要素のアドレス(ポインタ)が渡されます。そのため、関数側では配列のサイズを知るために、別途サイズ情報を引数として渡す必要があります。
#include <stdio.h>
// 配列の要素を表示する関数 (サイズも受け取る)
// void print_array(int arr[], size_t size) { // arr[] は int* arr と等価
void print_array(int* arr, size_t size) {
printf("[");
for (size_t i = 0; i < size; i++) {
printf("%d", arr[i]);
if (i < size - 1) {
printf(", ");
}
}
printf("]\n");
// sizeof(arr) はここではポインタのサイズになるため、要素数計算には使えない
}
// 2次元配列を関数に渡す場合 (列数は固定する必要がある)
void print_matrix(int mat[][3], int rows) { // 列数3を指定
// void print_matrix(int (*mat)[3], int rows) { // ポインタ表記
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", mat[i][j]);
}
printf("\n");
}
}
int main() {
int data[] = {10, 20, 30, 40, 50};
size_t n = sizeof(data) / sizeof(data[0]);
print_array(data, n); // 配列名(先頭アドレス)と要素数を渡す
int matrix_data[2][3] = {{1, 2, 3}, {4, 5, 6}};
print_matrix(matrix_data, 2); // 2次元配列名と行数を渡す
return 0;
}
可変長配列 (VLA – Variable Length Array) (C99以降)
配列のサイズを実行時に決定できる配列です(スタック領域に確保されることが多い)。関数スコープでのみ使用可能です。
注意: VLAは一部のコンパイラや環境ではサポートされていない、または推奨されていない場合があります。動的なサイズが必要な場合は、後述する動的メモリ確保 (malloc) を使うのが一般的です。
#include <stdio.h>
#include <stdlib.h>
void process_vla(int n) {
if (n <= 0) return;
int vla_array[n]; // サイズnの可変長配列 (スタック上に確保)
for(int i=0; i < n; i++) {
vla_array[i] = i * 10;
printf("vla_array[%d] = %d\n", i, vla_array[i]);
}
// 関数を抜けると自動的にメモリは解放される
}
int main(int argc, char *argv[]) {
if (argc > 1) {
int size = atoi(argv[1]); // コマンドライン引数からサイズを取得
if (size > 0 && size < 1000) { // 過大なサイズを防ぐ
process_vla(size);
} else {
printf("無効なサイズです。\n");
}
} else {
process_vla(5); // デフォルトサイズ
}
return 0;
}
7. ポインタ 👉
メモリ上のアドレスを格納するための変数です。変数の間接的な操作、動的メモリ確保、データ構造の構築などに不可欠です。
ポインタの宣言と初期化
- 宣言:
データ型 *ポインタ変数名;
- アドレスの取得:
&変数名
(アドレス演算子) - NULLポインタ: どこも指していないことを示す特別なポインタ (
NULL
マクロ、または(void*)0
)
int var = 10;
int *ptr; // int型へのポインタを宣言 (ptr自体のアドレスは別)
ptr = &var // ptrに変数varのアドレスを代入
int *ptr_null = NULL; // NULLポインタで初期化
double val = 3.14;
double *pd = &val
間接参照 (Dereference)
ポインタが指すアドレスに格納されている値にアクセスします。
*ポインタ変数名
(間接参照演算子)
int var = 25;
int *p = &var
printf("varの値: %d\n", var); // 25
printf("varのアドレス: %p\n", (void*)&var); // アドレス表示 (例: 0x7ff...)
printf("ptrが指すアドレス: %p\n", (void*)p); // varのアドレスと同じ
printf("ptrが指す先の値 (*p): %d\n", *p); // 25 (間接参照)
// ポインタ経由で値を変更
*p = 50;
printf("変更後のvarの値: %d\n", var); // 50 (varの値が変わる)
注意: 初期化されていないポインタやNULLポインタを間接参照すると、未定義動作(クラッシュなど)を引き起こします。
int *bad_ptr;
// *bad_ptr = 10; // ダメ! 未初期化ポインタの間接参照
int *null_ptr = NULL;
// if (null_ptr != NULL) { // 必ずチェックする
// *null_ptr = 20;
// }
ポインタ演算
ポインタに対して加算や減算を行うと、指し示すデータ型のサイズ単位でアドレスが移動します。
int arr[] = {10, 20, 30, 40};
int *ptr_arr = arr; // 配列名は先頭要素のアドレスを指すポインタとして扱われる (arr == &arr[0])
printf("ptr_arrが指す値: %d\n", *ptr_arr); // 10 (arr[0])
printf("ptr_arr+1が指す値: %d\n", *(ptr_arr + 1)); // 20 (arr[1])
printf("ptr_arr+2が指す値: %d\n", *(ptr_arr + 2)); // 30 (arr[2])
ptr_arr++; // ポインタを次の要素へ移動 (intのサイズ分アドレスが増加)
printf("インクリメント後のptr_arrが指す値: %d\n", *ptr_arr); // 20
// ポインタ同士の減算 (同じ配列内の要素を指す場合)
int *ptr1 = &arr[1];
int *ptr3 = &arr[3];
ptrdiff_t diff = ptr3 - ptr1; // 要素数の差 (型は ptrdiff_t)
printf("ポインタ間の要素数差: %td\n", diff); // 2
配列とポインタの関係
配列名は、多くの場合、その配列の先頭要素を指すポインタ(定数ポインタ)として扱われます。array[i]
は *(array + i)
と等価です。
int nums[] = {5, 15, 25};
printf("nums[1] = %d\n", nums[1]); // 15
printf("*(nums + 1) = %d\n", *(nums + 1)); // 15
int *p_num = nums;
printf("p_num[1] = %d\n", p_num[1]); // 15 (ポインタ変数でも配列のようにアクセス可能)
printf("*(p_num + 1) = %d\n", *(p_num + 1)); // 15
ポインタへのポインタ (ダブルポインタ)
ポインタ変数のアドレスを格納するポインタです。データ型 **ポインタ名;
のように宣言します。
int x = 100;
int *p1 = &x // xのアドレスを指すポインタ
int **p2 = &p1 // p1のアドレスを指すポインタ (ポインタへのポインタ)
printf("xの値: %d\n", x); // 100
printf("*p1の値: %d\n", *p1); // 100
printf("**p2の値: %d\n", **p2); // 100 (2回間接参照してxの値にアクセス)
printf("p1が指すアドレス: %p\n", (void*)p1); // xのアドレス
printf("*p2が指すアドレス: %p\n", (void*)*p2); // p1が指すアドレス (xのアドレス)
printf("p2が指すアドレス: %p\n", (void*)p2); // p1のアドレス
**p2 = 200; // p2経由でxの値を変更
printf("変更後のxの値: %d\n", x); // 200
ダブルポインタは、関数内でポインタ引数自体を変更したい場合や、ポインタの配列(文字列配列など)を扱う際によく使われます。
関数ポインタ
関数のアドレスを格納するポインタです。関数を引数として渡したり、コールバック関数を実現したりするのに使われます。
- 宣言:
戻り値の型 (*ポインタ名)(引数の型リスト);
#include <stdio.h>
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
// 関数ポインタを引数に取る関数
void calculate(int x, int y, int (*operation)(int, int)) {
int result = operation(x, y); // 関数ポインタ経由で関数を呼び出し
printf("Result: %d\n", result);
}
int main() {
int (*func_ptr)(int, int); // intを2つ引数に取りintを返す関数へのポインタ
func_ptr = add; // 関数名を代入 (関数のアドレス)
calculate(10, 5, func_ptr); // add関数を渡す -> Result: 15
func_ptr = subtract;
calculate(10, 5, func_ptr); // subtract関数を渡す -> Result: 5
// 関数ポインタの配列
int (*operations[2])(int, int) = {add, subtract};
printf("Via array[0]: %d\n", operations[0](20, 8)); // add(20, 8) -> 28
printf("Via array[1]: %d\n", operations[1](20, 8)); // subtract(20, 8) -> 12
return 0;
}
void ポインタ
特定の型を指さない汎用的なポインタです。void *
で宣言します。任意の型のポインタを代入できますが、使用(間接参照)する前に適切な型にキャストする必要があります。malloc
の戻り値などが void *
です。
#include <stdio.h>
#include <stdlib.h>
int main() {
int i = 10;
float f = 3.14f;
char c = 'A';
void *vp;
vp = &i
printf("void*経由 (intにキャスト): %d\n", *(int*)vp); // int* にキャストしてから間接参照
vp = &f
printf("void*経由 (floatにキャスト): %.2f\n", *(float*)vp); // float* にキャスト
vp = &c
printf("void*経由 (charにキャスト): %c\n", *(char*)vp); // char* にキャスト
// malloc の例
void *mem = malloc(sizeof(int));
if (mem != NULL) {
*(int*)mem = 123; // int* にキャストして値を書き込む
printf("mallocした領域の値: %d\n", *(int*)mem);
free(mem); // 解放
}
return 0;
}
8. 文字列操作 (string.h など) 📝
C言語では、文字列は文字 (char
) の配列として扱われ、通常、末尾にヌル文字 (\0
) が付加されます。
文字列リテラル
ダブルクォーテーション ("
) で囲まれた文字シーケンスです。読み取り専用メモリに格納されることが多く、変更は未定義動作となります。
char *str_literal = "Hello"; // ポインタはリテラルを指す (変更不可)
// str_literal[0] = 'J'; // 未定義動作!
char str_array[] = "World"; // 配列として初期化 (変更可能)
str_array[0] = 'F'; // OK -> "Forld"
基本的な文字列関数 (string.h)
関数 | 説明 | コード例 |
---|---|---|
strlen() |
文字列の長さ(ヌル文字を含まない)を返す (size_t 型) |
|
strcpy() |
文字列をコピーする (コピー先に十分な領域が必要)。バッファオーバーフローの危険あり |
|
strncpy() |
最大n文字まで文字列をコピーする。コピー元がn文字より短い場合、残りをヌル文字で埋める。n文字コピーしてヌル終端されない場合がある点に注意。 |
|
strcat() |
文字列を連結する (連結先に十分な領域が必要)。バッファオーバーフローの危険あり |
|
strncat() |
最大n文字まで文字列を連結する。常にヌル終端される。 |
|
strcmp() |
2つの文字列を辞書順で比較する。等しければ0、s1>s2なら正の値、s1<s2なら負の値を返す。 |
|
strncmp() |
最大n文字まで2つの文字列を比較する。 |
|
strchr() |
文字列の中から指定した文字が最初に出現する位置へのポインタを返す。見つからなければNULLを返す。 |
|
strrchr() |
文字列の中から指定した文字が最後に出現する位置へのポインタを返す。 |
|
strstr() |
文字列の中から指定した部分文字列が最初に出現する位置へのポインタを返す。見つからなければNULL。 |
|
セキュリティ警告: strcpy
や strcat
はバッファオーバーフローを引き起こしやすいため、strncpy
や strncat
、またはより安全な代替関数 (例: snprintf
, C11の strcpy_s
/strcat_s
など) の使用が推奨されます。
書式付き文字列操作 (stdio.h)
sprintf()
: 書式指定に従って文字列を生成し、文字配列に格納する。バッファオーバーフローの危険ありsnprintf()
:sprintf
と同様だが、最大書き込み文字数を指定でき、より安全。常にヌル終端される(バッファサイズが十分なら)。sscanf()
: 文字列から書式指定に従ってデータを読み取り、変数に格納する。
#include <stdio.h>
char buffer[100];
int year = 2025;
const char* event = "Cheatsheet Creation";
// snprintf を使用 (推奨)
int written = snprintf(buffer, sizeof(buffer), "Year: %d, Event: %s", year, event);
// written にはヌル文字を除いて書き込もうとした文字数が返る
if (written >= 0 && written < sizeof(buffer)) {
printf("Generated string: %s\n", buffer);
} else {
fprintf(stderr, "Buffer too small or error occurred.\n");
}
// sscanf の例
char data_str[] = "ID:123 Name:Alice Score:95.5";
int id;
char name[50];
float score;
int items_read = sscanf(data_str, "ID:%d Name:%49s Score:%f", &id, name, &score);
if (items_read == 3) {
printf("Read: ID=%d, Name=%s, Score=%.1f\n", id, name, score);
}
文字種別判定・変換 (ctype.h)
文字が英字か、数字か、空白かなどを判定したり、大文字小文字を変換したりする関数群です。
関数 | 説明 |
---|---|
isalpha(c) | 英字 (a-z, A-Z) かどうか |
isdigit(c) | 数字 (0-9) かどうか |
isalnum(c) | 英数字 (isalpha or isdigit) かどうか |
isspace(c) | 空白文字(スペース、タブ、改行など)かどうか |
islower(c) | 小文字 (a-z) かどうか |
isupper(c) | 大文字 (A-Z) かどうか |
isprint(c) | 表示可能文字(空白含む)かどうか |
iscntrl(c) | 制御文字かどうか |
ispunct(c) | 区切り文字(記号など)かどうか |
isxdigit(c) | 16進数字 (0-9, a-f, A-F) かどうか |
tolower(c) | 大文字を小文字に変換(それ以外はそのまま) |
toupper(c) | 小文字を大文字に変換(それ以外はそのまま) |
#include <ctype.h>
#include <stdio.h>
int main() {
char ch = 'a';
if (isalpha(ch)) { printf("%c is alpha\n", ch); }
char upper_ch = toupper(ch); // 'A'
printf("%c -> %c\n", ch, upper_ch);
char num_ch = '7';
if (isdigit(num_ch)) { printf("%c is digit\n", num_ch); }
return 0;
}
文字列と数値の変換 (stdlib.h)
関数 | 説明 |
---|---|
atoi(str) | 文字列を int に変換 |
atol(str) | 文字列を long に変換 |
atoll(str) | 文字列を long long に変換 (C99) |
atof(str) | 文字列を double に変換 |
strtol(str, endptr, base) | 文字列を long に変換(エラーチェック、基数指定可能) |
strtoul(str, endptr, base) | 文字列を unsigned long に変換(エラーチェック、基数指定可能) |
strtoll(str, endptr, base) | 文字列を long long に変換(C99、エラーチェック、基数指定可能) |
strtoull(str, endptr, base) | 文字列を unsigned long long に変換(C99、エラーチェック、基数指定可能) |
strtof(str, endptr) | 文字列を float に変換(C99、エラーチェック可能) |
strtod(str, endptr) | 文字列を double に変換(エラーチェック可能) |
strtold(str, endptr) | 文字列を long double に変換(C99、エラーチェック可能) |
strto*
系の関数は、変換できなかった部分のポインタを endptr
に格納したり、エラー発生時に errno
を設定したりするため、より堅牢な変換が可能です。
#include <stdlib.h>
#include <stdio.h>
#include <errno.h> // errno のため
int main() {
char num_str[] = "12345";
int num_i = atoi(num_str); // 簡単な変換
printf("atoi: %d\n", num_i);
char long_str[] = "9876543210";
char *end;
errno = 0; // errnoをクリア
long long num_ll = strtoll(long_str, &end, 10); // 10進数で変換
if (errno == ERANGE) {
printf("Error: Number out of range\n");
} else if (*end != '\0') {
printf("Error: Invalid characters found: %s\n", end);
} else {
printf("strtoll: %lld\n", num_ll);
}
char float_str[] = " -3.14e2rest";
errno = 0;
double num_d = strtod(float_str, &end);
if (errno == ERANGE) {
printf("Error: Number out of range\n");
} else {
printf("strtod: %f\n", num_d); // -314.000000
printf("Remaining string: %s\n", end); // "rest"
}
return 0;
}
9. 構造体と共用体 🧩
異なる型のデータをひとまとめにして扱うための仕組みです。
構造体 (struct)
複数の変数(メンバ)をグループ化します。各メンバは個別のメモリ領域を持ちます。
- 定義:
struct タグ名 { メンバ宣言リスト };
- 変数宣言:
struct タグ名 変数名;
- メンバアクセス:
変数名.メンバ名
(直接アクセス),ポインタ->メンバ名
(ポインタ経由アクセス) typedef
との組み合わせ:typedef struct { ... } 新しい型名;
#include <stdio.h>
#include <string.h>
// 構造体の定義 (タグ名: Point)
struct Point {
int x;
double y;
};
// typedef を使った構造体の定義 (型名: Person)
typedef struct {
char name[50];
int age;
float height;
} Person;
int main() {
// 構造体変数の宣言と初期化
struct Point p1; // 変数宣言
p1.x = 10; // メンバにアクセスして代入
p1.y = 20.5;
struct Point p2 = {30, 40.8}; // 初期化子リストで初期化
printf("p1: x=%d, y=%.1f\n", p1.x, p1.y);
printf("p2: x=%d, y=%.1f\n", p2.x, p2.y);
// typedef した型を使う
Person person1;
strcpy(person1.name, "Alice");
person1.age = 30;
person1.height = 165.5f;
Person person2 = {"Bob", 25, 175.0f};
printf("Person1: Name=%s, Age=%d, Height=%.1f\n", person1.name, person1.age, person1.height);
// 構造体へのポインタ
Person *ptr_person = &person1
// ポインタ経由でのメンバアクセス (アロー演算子 ->)
printf("Pointer access: Name=%s\n", ptr_person->name);
ptr_person->age = 31; // ポインタ経由で値を変更
printf("Updated Age: %d\n", person1.age); // person1の値も変わる
// ポインタ経由でのメンバアクセス (アスタリスクとドット) - やや煩雑
// (*ptr_person).age = 32;
// printf("Updated Age again: %d\n", (*ptr_person).age);
// 構造体のコピー (メンバごとにコピーされる)
Person person3 = person2;
printf("Person3 (copied from Person2): Name=%s\n", person3.name);
return 0;
}
共用体 (union)
複数のメンバが同じメモリ領域を共有します。一度に有効なメンバは1つだけであり、最後に代入されたメンバの値のみが保証されます。サイズは最も大きいメンバのサイズになります。
- 定義:
union タグ名 { メンバ宣言リスト };
- 変数宣言:
union タグ名 変数名;
- メンバアクセス: 構造体と同じ (
.
または->
)
メモリ使用量を節約したい場合や、複数の型を同じ場所で表現したい場合に使われます。
#include <stdio.h>
typedef union {
int i;
float f;
char c[4]; // int や float と同じサイズ (環境依存)
} Data;
int main() {
Data data;
printf("Size of Data union: %zu bytes\n", sizeof(Data)); // 最大メンバのサイズ
data.i = 12345;
printf("As int: %d\n", data.i);
// この時点で data.f や data.c の値は不定 (またはiのビット表現)
// printf("As float (after int assignment): %f\n", data.f); // 未定義動作に近い
data.f = 98.76f;
printf("As float: %f\n", data.f);
// この時点で data.i の値は不定 (またはfのビット表現)
// printf("As int (after float assignment): %d\n", data.i); // 未定義動作に近い
// 文字列としてアクセス (サイズに注意)
data.c[0] = 'A';
data.c[1] = 'B';
data.c[2] = 'C';
data.c[3] = '\0'; // ヌル終端
printf("As char array: %s\n", data.c);
// この時点で他のメンバの値は不定
return 0;
}
注意: 共用体のどのメンバが現在有効かを追跡するのはプログラマの責任です。構造体と組み合わせて、どのメンバが有効かを示すフラグを持つことがよくあります。
typedef enum { INT_TYPE, FLOAT_TYPE, CHAR_TYPE } DataType;
typedef struct {
DataType type;
union {
int i_val;
float f_val;
char c_val;
} value;
} Variant;
void print_variant(Variant v) {
switch(v.type) {
case INT_TYPE: printf("Int: %d\n", v.value.i_val); break;
case FLOAT_TYPE: printf("Float: %f\n", v.value.f_val); break;
case CHAR_TYPE: printf("Char: %c\n", v.value.c_val); break;
default: printf("Unknown type\n");
}
}
int main() {
Variant var1;
var1.type = INT_TYPE;
var1.value.i_val = 100;
print_variant(var1);
Variant var2;
var2.type = FLOAT_TYPE;
var2.value.f_val = 3.14f;
print_variant(var2);
return 0;
}
ビットフィールド
構造体メンバのサイズをビット単位で指定できます。メモリ使用量を極限まで切り詰めたい場合や、ハードウェアレジスタのように特定のビット配置を持つデータ構造を表現する場合に使われます。
#include <stdio.h>
// ビットフィールドを持つ構造体
typedef struct {
unsigned int flag1 : 1; // 1ビットのフラグ
unsigned int flag2 : 1; // 1ビットのフラグ
unsigned int type : 3; // 3ビット (0-7 の値を格納可能)
unsigned int value : 11; // 11ビット
// 合計 1 + 1 + 3 + 11 = 16ビット (通常2バイトに収まる)
// : の後の数字がビット数
} PackedData;
int main() {
PackedData data;
printf("Size of PackedData: %zu bytes\n", sizeof(PackedData));
data.flag1 = 1;
data.flag2 = 0;
data.type = 5; // 3ビットで表現可能 (0b101)
data.value = 1024; // 11ビットで表現可能 (0b10000000000)
printf("Flag1: %u\n", data.flag1);
printf("Flag2: %u\n", data.flag2);
printf("Type: %u\n", data.type);
printf("Value: %u\n", data.value);
// ビットフィールドの限界を超える値を代入すると、通常は切り捨てられる
// data.type = 10; // 3ビットの範囲を超える (未定義動作の可能性もある)
return 0;
}
注意: ビットフィールドのメモリレイアウト(ビットの順序など)は処理系依存(コンパイラやアーキテクチャに依存)です。移植性が重要な場合は注意が必要です。
10. ファイル操作 (stdio.h) 💾
ディスク上のファイルに対する読み書き操作です。
ファイルポインタ (FILE*)
ファイル操作を行うための中心的な概念です。fopen()
で取得し、fclose()
で解放します。
ファイルを開く・閉じる
関数 | モード | 説明 |
---|---|---|
fopen(filename, mode) |
"r" | 読み込み用に開く (ファイルが存在しないとエラー: NULLを返す) |
"w" | 書き込み用に開く (ファイルが存在すれば内容は消去、存在しなければ新規作成) | |
"a" | 追記用に開く (ファイルが存在すれば末尾から書き込み、存在しなければ新規作成) | |
"r+" | 読み書き両用に開く (ファイルが存在しないとエラー) | |
"w+" | 読み書き両用に開く (ファイルが存在すれば内容は消去、存在しなければ新規作成) | |
"a+" | 読み書き両用に開く (読み込みは先頭から、書き込みは末尾へ追記。存在しなければ新規作成) | |
上記モードに b を追加 (例: "rb" , "wb" ) するとバイナリモードで開く (テキストモードの改行変換などを行わない) | ||
fclose(fp) |
開いているファイルポインタ fp を閉じる。バッファの内容をフラッシュし、リソースを解放する。成功すれば0、エラーならEOFを返す。 |
#include <stdio.h>
#include <stdlib.h> // exit() のため
int main() {
FILE *fp_read = NULL;
FILE *fp_write = NULL;
// 読み込み用にファイルを開く
fp_read = fopen("input.txt", "r");
if (fp_read == NULL) {
perror("Error opening input.txt for reading"); // エラー内容を表示
// エラー処理 (例: プログラム終了)
// return 1;
} else {
printf("input.txt opened for reading.\n");
// ... 読み込み処理 ...
if (fclose(fp_read) != 0) {
perror("Error closing input.txt");
} else {
printf("input.txt closed.\n");
}
}
// 書き込み用にファイルを開く
fp_write = fopen("output.txt", "w");
if (fp_write == NULL) {
perror("Error opening output.txt for writing");
return 1; // エラー終了
} else {
printf("output.txt opened for writing.\n");
// ... 書き込み処理 ...
if (fclose(fp_write) != 0) {
perror("Error closing output.txt");
return 1;
} else {
printf("output.txt closed.\n");
}
}
return 0;
}
テキストファイルの読み書き
文字単位、行単位、書式付きでの読み書きが可能です。
関数 | 説明 |
---|---|
fgetc(fp) | ファイルから1文字読み込む (EOFまたはエラーでEOFを返す) |
fputc(c, fp) | ファイルへ1文字書き込む (成功すれば書き込んだ文字、エラーならEOFを返す) |
fgets(str, n, fp) | ファイルから1行読み込む (最大n-1文字または改行まで)。ヌル終端される。成功すればstr、EOFまたはエラーでNULLを返す。 |
fputs(str, fp) | ファイルへ文字列を書き込む (ヌル文字は書き込まない)。成功すれば非負の値、エラーならEOFを返す。 |
fscanf(fp, format, ...) | ファイルから書式付きで読み込む (scanf のファイル版)。成功した項目数を返す。 |
fprintf(fp, format, ...) | ファイルへ書式付きで書き込む (printf のファイル版)。書き込んだバイト数を返す。 |
// (fopen, fcloseは上記例を参照)
// --- fgetc / fputc の例 ---
int ch;
FILE *fin = fopen("input.txt", "r");
FILE *fout = fopen("output.txt", "w");
if (fin && fout) {
while ((ch = fgetc(fin)) != EOF) { // 1文字読んでEOFでなければループ
fputc(toupper(ch), fout); // 大文字に変換して書き込む
}
fclose(fin);
fclose(fout);
}
// --- fgets / fputs の例 ---
char line[256];
FILE *fin_line = fopen("config.ini", "r");
FILE *fout_line = fopen("config_copy.ini", "w");
if (fin_line && fout_line) {
while (fgets(line, sizeof(line), fin_line) != NULL) { // 1行読んでNULLでなければループ
fputs(line, fout_line); // 読み込んだ行をそのまま書き込む (fgetsは改行も読む)
}
fclose(fin_line);
fclose(fout_line);
}
// --- fscanf / fprintf の例 ---
int id;
char name[50];
float value;
FILE *fin_fmt = fopen("data.txt", "r"); // data.txt 例: "101 Alice 95.5"
FILE *fout_fmt = fopen("summary.txt", "w");
if (fin_fmt && fout_fmt) {
while (fscanf(fin_fmt, "%d %49s %f", &id, name, &value) == 3) { // 3項目読めたらループ
fprintf(fout_fmt, "ID: %d, Name: %s, Value: %.2f\n", id, name, value);
}
fclose(fin_fmt);
fclose(fout_fmt);
}
バイナリファイルの読み書き
バイト列としてデータをそのまま読み書きします。構造体などのデータをファイルに保存/復元するのに使われます。
関数 | 説明 |
---|---|
fread(ptr, size, count, fp) | ファイル fp から、size バイトのデータを count 個読み込み、メモリ領域 ptr に格納する。成功した個数 (count 未満の可能性あり) を返す。 |
fwrite(ptr, size, count, fp) | メモリ領域 ptr から、size バイトのデータを count 個、ファイル fp に書き込む。成功した個数 (count 未満の可能性あり) を返す。 |
#include <stdio.h>
typedef struct {
int id;
double data;
} Record;
int main() {
FILE *fbin_out = fopen("records.bin", "wb"); // バイナリ書き込みモード
if (!fbin_out) { perror("fopen wb"); return 1; }
Record rec1 = {1, 3.14};
Record rec2 = {2, 2.71};
Record records[] = {{3, 1.61}, {4, 0.57}};
// 1レコードずつ書き込み
fwrite(&rec1, sizeof(Record), 1, fbin_out);
fwrite(&rec2, sizeof(Record), 1, fbin_out);
// 配列をまとめて書き込み
fwrite(records, sizeof(Record), 2, fbin_out);
fclose(fbin_out);
// --- 読み込み ---
FILE *fbin_in = fopen("records.bin", "rb"); // バイナリ読み込みモード
if (!fbin_in) { perror("fopen rb"); return 1; }
Record read_rec;
size_t items_read;
printf("Reading records:\n");
// 1レコードずつ読み込み
while ((items_read = fread(&read_rec, sizeof(Record), 1, fbin_in)) == 1) {
printf("ID: %d, Data: %f\n", read_rec.id, read_rec.data);
}
if (!feof(fbin_in)) { // EOF以外の理由でループが終了した場合 (読み込みエラー)
perror("fread error");
}
fclose(fbin_in);
return 0;
}
注意: バイナリファイルは、エンディアン(バイトオーダ)や構造体のパディング(アラインメント)の違いにより、異なる環境間での互換性がない場合があります。
ファイル位置の制御
ファイル内の読み書き位置(ファイルポジションインジケータ)を移動させます。
関数 | 説明 | 起点 (whence) |
---|---|---|
fseek(fp, offset, whence) |
ファイルポインタ fp の位置を、起点 whence から offset バイト移動させる。成功すれば0、エラーなら非0を返す。 |
SEEK_SET (ファイルの先頭) |
SEEK_CUR (現在の位置) | ||
SEEK_END (ファイルの末尾) | ||
ftell(fp) |
現在のファイル位置(ファイル先頭からのバイト数)を返す (long 型)。エラーなら -1L を返す。 |
|
rewind(fp) |
ファイル位置をファイルの先頭に戻す (fseek(fp, 0L, SEEK_SET) とほぼ同じ)。エラーインジケータもクリアする。 |
|
fgetpos(fp, pos) |
現在のファイル位置を fpos_t 型の変数 pos に格納する。大きなファイル (>2GB) でも使える。成功なら0、エラーなら非0。 |
|
fsetpos(fp, pos) |
ファイル位置を fgetpos で取得した位置 pos に設定する。成功なら0、エラーなら非0。 |
#include <stdio.h>
int main() {
FILE *fp = fopen("sample.txt", "w+"); // 書き込み・読み込み両用で新規作成
if (!fp) { perror("fopen w+"); return 1; }
fputs("0123456789abcdef", fp);
// 先頭から5バイト目に移動
if (fseek(fp, 5L, SEEK_SET) == 0) {
long pos = ftell(fp);
printf("Current position: %ld\n", pos); // 5
// そこから文字を読み込む
int ch = fgetc(fp);
printf("Character at position 5: %c\n", (char)ch); // '5'
} else {
perror("fseek SEEK_SET");
}
// 末尾から3バイト手前に移動
if (fseek(fp, -3L, SEEK_END) == 0) {
long pos = ftell(fp);
printf("Current position (from end): %ld\n", pos); // 13 (0-based index)
int ch = fgetc(fp);
printf("Character 3 bytes from end: %c\n", (char)ch); // 'd'
} else {
perror("fseek SEEK_END");
}
// 先頭に戻る
rewind(fp);
long pos = ftell(fp);
printf("Position after rewind: %ld\n", pos); // 0
fclose(fp);
return 0;
}
エラー処理
関数 | 説明 |
---|---|
feof(fp) | ファイルポインタ fp がファイルの終端 (End Of File) に達しているかチェックする。EOFなら非0、そうでなければ0を返す。 |
ferror(fp) | ファイルポインタ fp に関連するエラーが発生したかチェックする。エラーがあれば非0、なければ0を返す。 |
clearerr(fp) | ファイルポインタ fp のファイル終端インジケータとエラーインジケータをクリアする。 |
perror(str) | 直前に発生したシステムエラー(errno に基づく)に対応するエラーメッセージを標準エラー出力に出力する。引数 str はメッセージの前に表示される文字列。 |
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
perror("Failed to open nonexistent.txt"); // perrorでエラー理由を表示
// 出力例: Failed to open nonexistent.txt: No such file or directory
} else {
// ... 読み書き処理 ...
if (ferror(fp)) {
fprintf(stderr, "An error occurred during file operation.\n");
clearerr(fp); // エラー状態をクリア
}
if (feof(fp)) {
printf("End of file reached.\n");
}
fclose(fp);
}
11. メモリ管理 (stdlib.h) 🧠
プログラム実行中に、必要なメモリ領域を動的に確保・解放する機能です。主にヒープ領域と呼ばれるメモリプールから割り当てられます。
動的メモリ確保
関数 | 説明 |
---|---|
malloc(size) | size バイトのメモリブロックをヒープ領域に確保し、その先頭アドレス (void* 型) を返す。確保された領域の内容は不定(初期化されない)。確保に失敗した場合は NULL を返す。 |
calloc(count, size) | size バイトの要素を count 個格納できるメモリブロック(合計 count * size バイト)を確保し、その先頭アドレス (void* ) を返す。確保された領域はゼロで初期化される。失敗した場合は NULL を返す。 |
realloc(ptr, new_size) | ptr が指す以前に確保されたメモリブロックのサイズを new_size バイトに変更する。新しいサイズが元のサイズより大きい場合、追加領域の内容は不定。小さい場合はデータが切り捨てられる。移動が発生する場合がある(その場合、元の ptr は無効になる)。成功すれば新しいメモリブロックのアドレス (void* )、失敗すれば NULL を返す(元のメモリは解放されない)。ptr が NULL の場合は malloc(new_size) と同じ。new_size が 0 の場合、free(ptr) と同じ(ただし戻り値は処理系依存)。 |
free(ptr) | malloc , calloc , realloc で確保されたメモリブロック ptr を解放し、ヒープ領域に戻す。解放済みのポインタや不正なポインタを free すると未定義動作となる。NULL ポインタを free しても何も起こらない。 |
#include <stdio.h>
#include <stdlib.h> // メモリ管理関数
#include <string.h> // memset
int main() {
int *numbers = NULL;
int n = 5;
// --- malloc ---
// int型n個分のメモリを確保
numbers = (int*)malloc(n * sizeof(int));
if (numbers == NULL) {
perror("malloc failed");
return 1;
}
printf("malloc allocated %zu bytes at %p\n", n * sizeof(int), (void*)numbers);
// malloc で確保した領域は初期化されていないため、必要なら初期化する
// 例: memset(numbers, 0, n * sizeof(int));
for (int i = 0; i < n; i++) {
numbers[i] = i * 10; // 値を代入
printf("numbers[%d] = %d\n", i, numbers[i]);
}
// malloc で確保したメモリは free で解放する必要がある
free(numbers);
numbers = NULL; // 解放後はNULLを入れておくのが安全 (ダングリングポインタ防止)
printf("Memory freed after malloc.\n\n");
// --- calloc ---
// double型n個分のメモリを確保し、ゼロで初期化
double *values = (double*)calloc(n, sizeof(double));
if (values == NULL) {
perror("calloc failed");
return 1;
}
printf("calloc allocated %zu bytes at %p (zero-initialized)\n", n * sizeof(double), (void*)values);
for (int i = 0; i < n; i++) {
printf("values[%d] = %f\n", i, values[i]); // 0.000000 が表示されるはず
}
// calloc で確保したメモリも free で解放
free(values);
values = NULL;
printf("Memory freed after calloc.\n\n");
// --- realloc ---
// 最初は3要素分確保
int *dynamic_array = (int*)malloc(3 * sizeof(int));
if (!dynamic_array) { perror("initial malloc for realloc failed"); return 1;}
dynamic_array[0] = 1; dynamic_array[1] = 2; dynamic_array[2] = 3;
printf("Initial allocation (3 elements) at %p\n", (void*)dynamic_array);
// 5要素分に拡張
int *temp_ptr = (int*)realloc(dynamic_array, 5 * sizeof(int));
if (temp_ptr == NULL) {
perror("realloc failed");
free(dynamic_array); // realloc失敗時は元のメモリを解放する必要がある場合も
return 1;
}
dynamic_array = temp_ptr; // realloc成功時はポインタを更新 (アドレスが変わる可能性があるため)
printf("Reallocated to 5 elements at %p\n", (void*)dynamic_array);
// 追加された要素 (dynamic_array[3], dynamic_array[4]) の値は不定
dynamic_array[3] = 4;
dynamic_array[4] = 5;
printf("Elements after realloc:");
for(int i=0; i<5; i++) printf(" %d", dynamic_array[i]);
printf("\n");
// realloc で確保/変更したメモリも free で解放
free(dynamic_array);
dynamic_array = NULL;
printf("Memory freed after realloc.\n");
return 0;
}
メモリ管理の注意点 ⚠️
- NULLチェック:
malloc
,calloc
,realloc
の戻り値は必ずNULL
でないかチェックする。 - 解放漏れ (Memory Leak): 確保したメモリは、不要になったら必ず
free
で解放する。解放し忘れると、プログラムが使用できるメモリが徐々に減っていく。 - 二重解放 (Double Free): 同じメモリ領域を2回以上
free
してはならない。未定義動作(クラッシュなど)を引き起こす。 - 解放後の使用 (Use After Free):
free
で解放した後のポインタ(ダングリングポインタ)を使用してはならない。未定義動作を引き起こす。解放後にポインタにNULL
を代入すると、誤用をある程度防げる。 - サイズ計算:
malloc
などでサイズを指定する際は、sizeof
を使って正確なバイト数を計算する。 - キャスト: Cでは
malloc
系の戻り値 (void*
) を代入先のポインタ型に暗黙的に変換できるため、キャストは必須ではないが、C++との互換性や明示性のためにキャストを書くことも多い。
12. プリプロセッサ ⚙️
コンパイルの前にソースコードを処理する機能です。#
で始まるディレクティブ(指示)によって制御されます。
ファイルインクルード (#include)
指定されたファイルの内容をその場所に挿入します。
#include <filename>
: システム標準のヘッダファイルをインクルードする(標準ライブラリなど)。コンパイラが定義済みの検索パスから探す。#include "filename"
: ユーザー定義のヘッダファイルをインクルードする。通常はカレントディレクトリや指定されたインクルードパスから探す。
#include <stdio.h> // 標準入出力ライブラリ
#include <stdlib.h> // 標準ユーティリティライブラリ
#include "my_header.h" // 自作のヘッダファイル
マクロ定義 (#define)
識別子(マクロ名)を特定の文字列(トークンシーケンス)で置き換えるルールを定義します。定数定義や簡単な関数のような置換(関数形式マクロ)に使われます。
- オブジェクト形式マクロ (定数):
#define マクロ名 置換内容
- 関数形式マクロ:
#define マクロ名(引数リスト) 置換内容
#define PI 3.14159
#define BUFFER_SIZE 1024
#define GREETING "Hello"
// 関数形式マクロ (引数は括弧で囲むことが多い)
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
int main() {
double radius = 5.0;
double area = PI * SQUARE(radius); // PI * ((radius) * (radius)) に展開される
printf("Area: %f\n", area);
char buffer[BUFFER_SIZE];
printf("%s\n", GREETING);
int max_val = MAX(10, 20); // ((10 > 20) ? 10 : 20) に展開 -> 20
printf("Max: %d\n", max_val);
// 副作用に注意!
int i = 5;
int j = SQUARE(i++); // ((i++) * (i++)) のように展開される可能性があり、未定義動作に近い
// iが2回インクリメントされるか、順序も保証されない
printf("i=%d, j=%d (undefined behavior risk!)\n", i, j);
return 0;
}
関数形式マクロの注意点:
- 置換後の式の優先順位問題を避けるため、引数と式全体を括弧
()
で囲むことが強く推奨される。 - 引数に副作用(
i++
など)を持つ式を渡すと、予期せぬ結果や未定義動作を引き起こす可能性がある。 - 型チェックが行われない。
- デバッグが難しくなることがある。
現代のC/C++では、定数には const
変数や enum
、簡単な関数的処理にはインライン関数 (inline
) を使う方が安全で推奨される場合が多いです。
マクロの未定義化 (#undef)
#define
で定義されたマクロを無効化します。
#define DEBUG_MODE 1
// ... DEBUG_MODE を使ったコード ...
#undef DEBUG_MODE
// ここ以降、DEBUG_MODE は未定義となる
// #ifdef DEBUG_MODE // これは偽になる
// printf("Debug is still active?\n");
// #endif
条件付きコンパイル
特定の条件に基づいて、ソースコードの一部をコンパイル対象に含めたり除外したりします。デバッグコードの切り替え、環境依存コードの分離などに使われます。
#if 条件式
#ifdef マクロ名
(マクロが定義されていれば真)#ifndef マクロ名
(マクロが定義されていなければ真)#elif 条件式
(else if)#else
#endif
(ブロックの終わり)defined(マクロ名)
:#if
や#elif
内でマクロが定義されているかチェックする演算子。
#define VERSION 2
#define ENABLE_LOGGING
int main() {
#if VERSION > 1
printf("Using Version 2 or later features.\n");
#else
printf("Using legacy features.\n");
#endif
#ifdef ENABLE_LOGGING
printf("Logging is enabled.\n");
// ログ出力コード
#endif
#ifndef MAX_USERS
#define MAX_USERS 100 // MAX_USERSが未定義なら定義する
printf("MAX_USERS defined as 100.\n");
#endif
#if defined(_WIN32) // Windows環境かどうか (一般的な定義済みマクロ)
printf("Compiling for Windows.\n");
#elif defined(__linux__) // Linux環境かどうか
printf("Compiling for Linux.\n");
#elif defined(__APPLE__) // macOS環境かどうか
printf("Compiling for macOS.\n");
#else
printf("Compiling for an unknown OS.\n");
#endif
return 0;
}
ヘッダファイルの多重インクルード防止(インクルードガード)によく #ifndef / #define / #endif
が使われます。
// my_header.h
#ifndef MY_HEADER_H // このマクロが未定義なら(初めてインクルードされたら)
#define MY_HEADER_H // マクロを定義する
// ヘッダファイルの内容
struct MyStruct { int data; };
void my_function(void);
#endif // MY_HEADER_H の終わり
または、より現代的な #pragma once
ディレクティブも使われます(多くのコンパイラがサポート)。
// my_other_header.h
#pragma once // このヘッダを1回だけインクルードするようにコンパイラに指示
// ヘッダファイルの内容
// ...
エラー・警告指示 (#error, #warning)
#error メッセージ
: コンパイルを停止し、指定したエラーメッセージを表示する。#warning メッセージ
: コンパイルを続行するが、指定した警告メッセージを表示する(コンパイラによってはサポートされない)。
#ifndef REQUIRED_FEATURE
#error "Required feature macro is not defined! Compilation aborted."
#endif
#if PLATFORM_VERSION < 2
#warning "Using an older platform version. Some features might be limited."
#endif
行番号制御 (#line)
コンパイラが報告するエラーメッセージなどの行番号とファイル名を変更します。コードジェネレータなどで使われることがあります。
// ... コード ...
#line 100 "generated_code.c"
// ここから下のコードのエラーは "generated_code.c" の 100行目以降として報告される
int generated_variable = 0; // この行は 100行目扱い
// ...
プラグマ指示 (#pragma)
コンパイラ固有の機能や動作を制御するための指示です。内容はコンパイラによって大きく異なります。#pragma once
はその一例です。
// 例: 特定の警告を一時的に無効化 (コンパイラ依存)
#pragma warning(push)
#pragma warning(disable : 4996) // 非推奨関数の警告を無効化 (MSVCの例)
strcpy(dest, src); // 本来なら警告が出るかもしれない strcpy
#pragma warning(pop)
定義済みマクロ
コンパイラや環境によって、事前に定義されているマクロがあります。これらを使うと、コード内でファイル名や行番号、コンパイル日時などを利用できます。
マクロ | 説明 |
---|---|
__FILE__ | 現在のソースファイル名 (文字列リテラル) |
__LINE__ | 現在のソースコードの行番号 (整数) |
__DATE__ | コンパイルされた日付 ("Mmm dd yyyy" 形式の文字列リテラル) |
__TIME__ | コンパイルされた時刻 ("hh:mm:ss" 形式の文字列リテラル) |
__func__ | 現在の関数名 (文字列リテラル, C99以降) |
__STDC__ | コンパイラが標準Cに準拠している場合に 1 に定義される |
__STDC_VERSION__ | サポートするC標準のバージョンを示す (例: 201112L for C11, C99以降) |
#include <stdio.h>
void log_message(const char* message) {
// __func__ は関数内でのみ利用可能
printf("[%s %s %d] (%s): %s\n", __DATE__, __TIME__, __LINE__, __func__, message);
}
int main() {
printf("Compiling file: %s\n", __FILE__);
log_message("This is a log entry.");
#ifdef __STDC_VERSION__
printf("C Standard Version: %ld\n", __STDC_VERSION__);
#else
printf("C Standard Version not specified (likely pre-C99).\n");
#endif
return 0;
}
13. その他Tips ✨
列挙型 (enum)
関連する整数定数に名前を付ける方法です。コードの可読性が向上します。
#include <stdio.h>
// 曜日の列挙型
typedef enum {
SUNDAY, // 0 (デフォルトで0から始まる)
MONDAY, // 1
TUESDAY, // 2
WEDNESDAY, // 3
THURSDAY, // 4
FRIDAY, // 5
SATURDAY // 6
} DayOfWeek;
// 開始値を指定することも可能
typedef enum {
RED = 10,
GREEN, // 11
BLUE = 20,
YELLOW // 21
} ColorCode;
int main() {
DayOfWeek today = WEDNESDAY;
ColorCode sky_color = BLUE;
if (today == SATURDAY || today == SUNDAY) {
printf("It's weekend!\n");
} else {
printf("It's weekday (%d).\n", today); // today は 3
}
printf("Sky color code: %d\n", sky_color); // sky_color は 20
return 0;
}
インライン関数 (inline) (C99以降)
関数呼び出しのオーバーヘッドを削減するために、コンパイラに関数本体を呼び出し元に展開するよう要求するヒントです。短い関数に適しています。最終的にインライン化されるかはコンパイラの判断によります。
#include <stdio.h>
// インライン関数の定義 (ヘッダファイルに書くことが多い)
static inline int min(int a, int b) {
return (a < b) ? a : b;
}
// static inline にすると、この翻訳単位内でのみ有効な内部リンケージのインライン関数となることが多い
int main() {
int x = 10, y = 5;
int minimum = min(x, y); // ここが (x < y) ? x : y に展開されるかもしれない
printf("Minimum of %d and %d is %d\n", x, y, minimum);
return 0;
}
関数形式マクロよりも型安全で、デバッグもしやすい利点があります。
コメント
- 単一行コメント:
// コメント内容
(C99以降、多くのコンパイラが拡張機能としてC89でもサポート) - 複数行コメント:
/* コメント内容 */
(C89からサポート)
// これは単一行コメントです
int main() {
/*
これは
複数行にわたる
コメントです。
*/
int value = 10; // 変数の説明
return 0; /* プログラム終了 */
}
注意: 複数行コメント /* ... */
はネストできません (/* コメント /* ネスト */ 終了 */
はエラー)。
より安全な関数の使用 (C11 Annex K – オプショナル)
C11標準では、バッファオーバーフローなどの問題を緩和するための、より安全なバージョンの関数群 (_s
接尾辞が付くもの) が提案されました (Annex K Bounds-checking interfaces)。しかし、これらの関数のサポートはコンパイラによって異なり、広く普及しているとは言えません。
strcpy_s()
strcat_s()
strncpy_s()
strncat_s()
sprintf_s()
scanf_s()
fopen_s()
gets_s()
(gets
はC11で廃止)- など
これらの関数は、バッファサイズを引数として明示的に取るなどの改良が加えられています。
#define __STDC_WANT_LIB_EXT1__ 1 // 安全な関数を使う意思表示 (コンパイラ依存)
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // errno_t のため (環境による)
int main() {
char dest[10];
char src[] = "This is too long";
errno_t err; // エラーコードを受け取る型 (環境による)
// strcpy_s の例 (MSVCなど)
err = strcpy_s(dest, sizeof(dest), src);
if (err == 0) {
printf("strcpy_s successful: %s\n", dest);
} else {
printf("strcpy_s failed! Error code: %d\n", err);
// エラー処理 (例: バッファが小さすぎる場合など)
if (err == STRUNCATE) { // (エラーコードは環境依存)
printf("Source string was truncated.\n");
// destは空文字列などに設定される場合がある
}
}
// scanf_s の例 (MSVCなど) - 文字列読み込み時にバッファサイズを指定
char name[20];
printf("Enter name (max 19 chars): ");
// scanf("%19s", name); // 通常の scanf (サイズ指定は書式文字列内)
int result = scanf_s("%19s", name, (unsigned)_countof(name)); // バッファサイズを引数で渡す (_countofはMSVC拡張)
// 標準では sizeof(name) 相当
if (result == 1) {
printf("Hello, %s\n", name);
}
return 0;
}
推奨: Annex K 関数が利用できない環境も多いため、snprintf
, strncpy
(ヌル終端確認必須), strncat
などを注意深く使うか、他の安全な文字列ライブラリを利用することが一般的です。
コメント