結論

C言語とは、1972年に生まれた、コンパイルして機械語で動かすプログラミング言語です。OSや組み込み機器など、速さとハードに近い操作が要る所で使われます。

この記事でわかること

  • gccの4段階(-E・-S・-c・リンク)の中間ファイル
  • sizeof 14型の実測とポインタのアドレスの実物
  • エラー原文15件の直し方とvalgrind・ASanの出力

2026年10月22日更新|GCC 13.3.0・Ubuntu 24.04 で実行確認

C言語のプログラムは、gcc などのコンパイラが4つの段階を経て、CPU が直接実行できるファイルに変えます。

flowchart TD
    A[hello.c<br>書いたソース] -->|gcc -E 前処理| B[hello.i<br>820行に展開]
    B -->|gcc -S コンパイル| C[hello.s<br>アセンブリ]
    C -->|gcc -c アセンブル| D[hello.o<br>機械語の部品]
    D -->|リンク| E[hello<br>実行ファイル]

最初によく使う書き方とコマンドです。

やりたいこと 書き方・コマンド 補足
コンパイルして実行 gcc hello.c -o hello → ./hello Windows は hello.exe ができる
警告をすべて出す gcc -Wall -Wextra hello.c -o hello 初めは必ず付ける
型の大きさを調べる sizeof(int) 64ビット Linux で4バイト
変数のアドレスを表示 printf("%p", (void *)&x) 実行のたびに変わる
メモリを確保・解放 malloc → 使う → free NULL かどうかを確かめる
メモリの誤りを探す gcc -g -fsanitize=address valgrind でも探せる
数学関数を使う gcc calc.c -lm 無いと undefined reference to `sqrt'
Windows で始める Visual Studio の cl、MSYS2 の gcc 環境構築の節に手順

確認環境:Ubuntu 24.04.4・GCC 13.3.0(既定の規格は gnu17)・GNU ld 2.42・glibc 2.39・Valgrind 3.22.0・GDB 15.1・Clang 18.1.3(エラー文の比較)・OpenJDK 21.0.10・Python 3.11.15(速さの比較)。この記事のC言語のコードは、すべて gcc でコンパイルして実行し、出力・エラー文・ファイルサイズを載せています。gcc のオプションは GCC 公式マニュアル、GCC 14・15 の変更は GCC のリリースノート、規格は ISO のページ、Windows の手順は Microsoft Learn と VS Code のドキュメント、Mac の手順は Apple Developer のドキュメント、AddressSanitizer は Clang のドキュメント、Valgrind は Valgrind のマニュアル、言語の順位は TIOBE Index で確かめました(確認日 2026年10月22日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「C言語とは|入門から環境構築・ポインタ・mallocまで実行例で解説」

C言語とは(意味・歴史・規格)

この節では、C言語がどんな言語で、いつ・誰が作り、どんな規格があるかが分かります。

C言語(シーげんご)は、1972年にベル研究所のデニス・リッチーが作ったプログラミング言語です。 前身の「B言語」を改良したことから「C」と名付けられ、UNIX とともに広まりました。書いたソースコードを、コンパイラで機械語(CPU が直接読む命令)に変えてから実行する「コンパイル型」の言語です。

1手続き型

処理を上から順に書き、関数にまとめて組み立てる。クラス(オブジェクト指向)は無い。

2コンパイル型

実行前に機械語へ変える。実行時に翻訳しないぶん速い。

3ハードに近い

ポインタでメモリの番地を直接扱える。ビット単位の操作もできる。

4移植しやすい

多くの CPU・OS にコンパイラがある。同じソースを別の機械用にコンパイルし直せる。

C言語の規格(C89〜C23)

C言語の文法は、国際規格 ISO/IEC 9899 で決められています。版ごとに通称があり、gcc では -std=c99 のように選べます。__STDC_VERSION__ は、コンパイラがどの規格でコンパイルしているかを表す値で、GCC 13.3.0 で確かめた値を載せています。

通称 規格 発行 __STDC_VERSION__
C89・C90 ANSI X3.159-1989・ISO/IEC 9899:1990 1989年・1990年 定義なし
C99 ISO/IEC 9899:1999 1999年 199901L
C11 ISO/IEC 9899:2011 2011年 201112L
C17(C18) ISO/IEC 9899:2018 2018年 201710L
C23 ISO/IEC 9899:2024 2024年10月 202311L(GCC 15)

2026年10月22日時点で、ISO のページでは ISO/IEC 9899:2024(C23)が発行済み(Published)です。GCC 13 の既定は gnu17(C17 に GNU の拡張を足したもの)でした。GCC 15 のリリースノートには「C のコンパイルの既定を -std=gnu17 から -std=gnu23 に変えた」とあります。

$ echo | gcc -dM -E - | grep __STDC_VERSION__
#define __STDC_VERSION__ 201710L

C言語の読み方と「C」の名前

読み方は「シーげんご」です。英語では単に C(シー)、または C programming language と呼びます。後で比べる C++(シープラスプラス)と C#(シーシャープ)は、名前は似ていますが別の言語です。

C言語でできること・使われている場所

この節では、C言語が今どこで使われているかと、今も使われる理由が分かります。

C言語は、速さ・メモリの少なさ・ハードウェアの直接操作が求められるソフトウェアで使われています。

分野 例 C言語が選ばれる理由
OS Linux カーネル メモリ・CPU を直接扱える
組み込み機器 家電・自動車の制御、マイコン 小さいメモリで動き、実行が速い
データベース・サーバー SQLite・PostgreSQL・nginx 速さとメモリの効率
言語の処理系・ライブラリ Python の拡張モジュール 他の言語から呼び出しやすい
デバイスドライバ ネットワーク・ストレージの制御 ハードウェアのレジスタを操作する
ゲームエンジン・画像処理 処理の重い部分のライブラリ 計算が速い

公式の文書に書かれている例です。

  • Linux カーネルのドキュメント:「The Linux kernel is written in the C programming language」(gcc の -std=gnu11 でコンパイルされることが多い、とあります。Rust も CONFIG_RUST で使えるようになっています)
  • SQLite の公式サイト:「SQLite is a C-language library」
  • Python の公式ドキュメント:C で「C API extension module(拡張モジュール)」を作る手順が載っている

これらの分野の開発者は、組み込みエンジニア・ミドルウェアの開発者・ネットワーク機器の開発者などと呼ばれます。

TIOBE Index(プログラミング言語の人気の指標)の2026年10月の順位では、C は Python に次ぐ2位(11.33%)でした。

1972年C言語が生まれた年ベル研究所のデニス・リッチー
2位TIOBE Index の順位2026年10月。11.33%
C232026年10月時点の最新規格ISO/IEC 9899:2024

C言語の特徴(メリットとデメリット)

この節では、C言語の良い点と気を付ける点を、この記事の実測と合わせて見ます。

メリット

実行が速い(素数を数える処理で Python の約30倍。後の節で実測)

使うメモリが少ない(同じ処理で約11MB)

メモリやハードウェアを直接扱える

多くの CPU・OS で動く。他の言語から呼び出しやすい

文法が小さい。コンピューターの仕組みを学べる

デメリット

メモリの確保と解放を自分で書く

配列の範囲外を読んでもエラーにならないことがある

誤りがクラッシュ(Segmentation fault)として出る

文字列・リストなどの便利な型が少ない

クラスが無く、大きなプログラムは設計の工夫が要る

「エラーにならない」ことがある:未定義動作

C言語の規格には「未定義動作(undefined behavior)」という考え方があります。規格が「どうなるかを決めない」操作で、配列の範囲外の読み書きや、int のあふれ(オーバーフロー)などが当たります。 コンパイラも実行時の仕組みも、多くの場合は止めてくれません。

int big = INT_MAX;
int arr[3] = {1, 2, 3};
int idx = 3;
printf("INT_MAX + 1 = %d\n", big + 1);
printf("arr[3] = %d\n", arr[idx]);
INT_MAX + 1 = -2147483648
arr[3] = -106207488

どちらもエラーにならず、意味のない値が出ました。同じことを Java と Python で行うと、実行時に止まります。

要素3個の配列で arr[3] を読んだとき

C言語arr[3] = -106207488(そのまま進む)
→
Java・PythonArrayIndexOutOfBoundsException・IndexError で止まる

C言語では範囲を守るのは書く人の役目。検出には後の節の -fsanitize が使える

-fsanitize=undefined,address を付けてコンパイルし直すと、実行時に2つとも見つかりました。

ub.c:9:5: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
ub.c:10:32: runtime error: index 3 out of bounds for type 'int [3]'

C言語とJava・Python・C++の違い

この節では、他の言語と何が違うかを、同じ処理を動かした結果と表で比べます。

Java・Pythonとの違い

1千万以下の素数の個数を数える同じ処理(エラトステネスのふるい)を、C・Java・Python で書いて5回ずつ実行し、真ん中の時間を取りました。答えはどれも 664579 でした。

0.05秒C(gcc -O2)最適化なし(-O0)は 0.13 秒。使ったメモリ約11MB
0.12秒Java 21JVM の起動を含む。使ったメモリ約50MB
1.55秒Python 3.11使ったメモリ約17MB

時間はプロセスの起動から終了までで、処理の内容や書き方で大きく変わります。ここでは「C は速い」ことの目安として載せています。

項目 C言語 Java Python JavaScript
実行のしかた 機械語にコンパイルして直接実行 バイトコードにして JVM で実行 インタプリタが読みながら実行 ブラウザ・Node.js のエンジンが実行
メモリの解放 自分で free を書く GC(自動) 自動(参照カウントと GC) GC(自動)
型 静的(宣言が要る) 静的 動的(宣言しない) 動的
文字列 char の配列+最後に '\0' String クラス str 型 string 型
配列の範囲外 検査しない(未定義動作) 例外で止まる 例外で止まる undefined を返す
クラス 無い(構造体と関数) ある ある ある
実行ファイル OS・CPU ごとに作り直す .class は JVM があれば動く ソースのまま動く ソースのまま動く

範囲外の欄は、要素3個の配列の [3] を読んだ結果です(Java 21 は ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3、Python は IndexError: list index out of range、Node.js 22 は undefined)。

C++・C#との違い

項目 C言語 C++ C#
関係 もとの言語 C を拡張して作られた 名前は似ているが別系統
オブジェクト指向 無い ある(クラス・継承) ある
メモリ管理 手動 手動+スマートポインタ GC(自動)
主な用途 OS・組み込み・ライブラリ ゲーム・アプリ・高速な処理 Windows アプリ・Unity のゲーム
C のコード ― 多くはそのままコンパイルできる そのままは使えない

Objective-C も C を拡張した言語で、Apple の macOS・iOS のアプリ開発で使われてきました。C言語を学んでおくと、C++ の土台・Java や Go のポインタや参照の考え方・Rust がメモリの安全を守る理由がつかみやすくなります。

C言語の環境構築(Windows・Mac・Linux)

この節では、OS ごとにC言語をコンパイルできるようにする手順が分かります。

C言語を動かすには「コンパイラ」が要ります。 代表は gcc(GNU)、clang(LLVM)、MSVC(Microsoft の cl)の3つです。

OS 入れるもの 確かめるコマンド
Windows(手軽さ重視) Visual Studio の「C++ によるデスクトップ開発」 開発者コマンド プロンプトで cl
Windows(gcc を使う) MSYS2 の MinGW-w64 gcc --version
Windows(Linux と同じ環境) WSL の Ubuntu に build-essential gcc --version
Mac Command Line Tools for Xcode(clang) clang --version
Linux(Ubuntu・Debian) sudo apt install build-essential gcc --version

Windows:Visual Studio(cl)

Microsoft Learn のチュートリアルの手順です。

1

Visual Studio を入れる

Visual Studio インストーラーで「C++ によるデスクトップ開発」ワークロードを選んでインストールします。

Build Tools for Visual Studio でもよい

2

開発者コマンド プロンプトを開く

スタートメニューの「Visual Studio 2022」フォルダーから「開発者コマンド プロンプト for VS 2022」を開き、cl と打ってコンパイラの版が出るか見ます。

普通のコマンドプロンプトでは cl が見つからない

3

コンパイルして実行する

cl hello.c で hello.exe ができます。hello と打つと実行されます。

.c で保存すると C としてコンパイルされる

Windows:MSYS2 の gcc

VS Code のドキュメントにある手順です。MSYS2 をインストールし、MSYS2 のターミナルで次を実行します。

pacman -S --needed base-devel mingw-w64-ucrt-x86_64-toolchain

続けて、Windows の環境変数 Path に C:\msys64\ucrt64\bin(既定の場所の場合)を足し、新しく開いたコマンドプロンプトで gcc --version を実行して版が出れば準備完了です。

Mac:Command Line Tools for Xcode

Apple のドキュメントの手順です。ターミナルで次を実行し、表示されたダイアログで「インストール」を選びます。

xcode-select --install

clang などのコマンドが /Library/Developer/CommandLineTools に入ります。Xcode 本体を入れている場合は、別に入れなくても使えます。コンパイルは clang hello.c -o hello、実行は ./hello です。

Linux(Ubuntu・WSL)

Ubuntu の build-essential は、gcc・g++・make・libc6-dev(標準ライブラリのヘッダー)をまとめて入れるパッケージです。

sudo apt update
sudo apt install build-essential
gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.

エディターは VS Code・Vim など何でも使えます。VS Code の日本語化と入れ方は、関連記事「VSCode日本語化とインストール手順|Win/Mac/Linuxと直し方」で扱っています。

Hello Worldを書いて動かす

この節では、最初のプログラムを書いて、コンパイル・実行するまでが分かります。

hello.c という名前で保存します。

#include <stdio.h>

int main(void)
{
    printf("Hello, World!\n");
    return 0;
}
行 意味
#include <stdio.h> 入出力の関数(printf など)の宣言を読み込む
int main(void) プログラムが始まる関数。名前は必ず main
printf("Hello, World!\n"); 文字を表示する。\n は改行。文の最後に ;
return 0; 正常に終わったことを OS に伝える(終了コード 0)

コンパイルして実行しました。$? は直前のコマンドの終了コードです。

$ gcc hello.c -o hello
$ ./hello
Hello, World!
$ echo $?
0

-o hello は「出来上がるファイルの名前を hello にする」指定です。省くと a.out(Windows の MinGW では a.exe)という名前になります。

コンパイルの仕組み(gccの-E・-S・-c・リンク)

この節では、gcc hello.c -o hello の1行の中で何が起きているかを、途中のファイルを開いて確かめます。

gcc は「前処理 → コンパイル → アセンブル → リンク」の4段階で実行ファイルを作ります。 オプションを付けると、好きな段階で止められます。GCC のマニュアルにある説明と、実際にできたファイルです。

段階 止めるオプション できるもの 大きさ(実測)
ソース ― hello.c 84 バイト
1. 前処理 -E hello.i(#include を展開したソース) 820 行・21,316 バイト
2. コンパイル -S hello.s(アセンブリ) 668 バイト
3. アセンブル -c hello.o(オブジェクトファイル) 1,504 バイト
4. リンク (何も付けない) hello(実行ファイル) 15,960 バイト

1. 前処理(-E):#include が展開される

#include <stdio.h> の行が、stdio.h の中身に置き換わります。7行のソースが820行になりました。その中に printf の宣言があります。

$ gcc -E hello.c -o hello.i
$ wc -l hello.i
820 hello.i
$ grep -n "extern int printf" hello.i
476:extern int printf (const char *__restrict __format, ...);
$ tail -6 hello.i
int main(void)
{
    printf("Hello, World!\n");
    return 0;
}

2. コンパイル(-S):アセンブリになる

CPU の命令を人が読める形で書いたもの(アセンブリ)ができます。主な部分を抜き出しました。

$ gcc -S hello.c -o hello.s
$ cat hello.s
.LC0:
    .string "Hello, World!"
    .text
    .globl  main
    .type   main, @function
main:
    ...
    leaq    .LC0(%rip), %rax
    movq    %rax, %rdi
    call    puts@PLT
    movl    $0, %eax
    popq    %rbp
    ret

printf と書いたのに、puts を呼ぶ命令になっていました。 書式(%d など)の無い文字列を改行付きで出すだけなので、gcc がより単純な puts に置き換えています。最適化を指定しない(-O0)状態でも、この置き換えが行われていました。

3. アセンブル(-c):オブジェクトファイルになる

機械語になりましたが、まだ実行できません。nm で中身の名前を見ると、puts は U(undefined=このファイルには無い)でした。

$ gcc -c hello.c -o hello.o
$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
$ nm hello.o
0000000000000000 T main
                 U puts

4. リンク:足りない部品をつないで実行ファイルにする

リンカー(ld)が、puts の本体がある標準ライブラリ(libc)とつなぎます。

$ gcc hello.o -o hello
$ file hello
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, ...
$ ldd hello
    linux-vdso.so.1 (0x00007f93af0a9000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f93aee00000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f93af0ab000)

dynamically linked は「libc を実行のときに読み込む」という意味です。-static を付けると libc の中身を実行ファイルに入れるため、大きさは 15,960 バイトから 785,360 バイトになりました。途中のファイルを全部残したいときは gcc -save-temps hello.c -o hello で、hello.i・hello.s・hello.o が同じフォルダにできます。gcc -v を付けると、実際に呼ばれている cc1(コンパイラ本体)・as(アセンブラ)・collect2(リンクの入口)が表示されます。

ファイルを分けてコンパイルする(分割コンパイル)

プログラムが大きくなったら、関数ごとにファイルを分けます。宣言はヘッダーファイル(.h)に書き、使う側で #include "calc.h" します。

/* calc.h */
#ifndef CALC_H
#define CALC_H
int add(int a, int b);
#endif
$ gcc -Wall -c main.c
$ gcc -Wall -c calc.c
$ gcc main.o calc.o -o app
$ ./app
2 + 3 = 5
$ gcc main.o -o app2
/usr/bin/ld: main.o: in function `main':
main.c:(.text+0x13): undefined reference to `add'
collect2: error: ld returned 1 exit status

calc.o を渡し忘れると、リンクの段階で undefined reference to `add' になります。ファイルが増えたときの自動化は、関連記事「Makefileの書き方|自動変数一覧とタスクランナー用テンプレート」で扱っています。

変数と型(sizeofの実測)

この節では、C言語の基本の型と、それぞれが何バイトかを実際に測った結果が分かります。

C言語の変数は、使う前に型を決めて宣言します。 型の大きさは規格で完全には決まっておらず、OS と CPU で変わります。sizeof で実際の大きさを測りました(64ビットの Ubuntu・GCC 13.3.0)。

printf("int         : %zu バイト (%d 〜 %d)\n", sizeof(int), INT_MIN, INT_MAX);
char        : 1 バイト (-128 〜 127)
short       : 2 バイト (-32768 〜 32767)
int         : 4 バイト (-2147483648 〜 2147483647)
unsigned int: 4 バイト (0 〜 4294967295)
long        : 8 バイト (-9223372036854775808 〜 9223372036854775807)
long long   : 8 バイト
float       : 4 バイト (有効桁 6 桁)
double      : 8 バイト (有効桁 15 桁)
long double : 16 バイト
bool        : 1 バイト
int32_t     : 4 バイト
size_t      : 8 バイト
int *       : 8 バイト
char *      : 8 バイト
型 64ビット Linux(実測) Windows(MSVC) 主な使い道
char 1 1 1文字・1バイト
int 4 4 ふつうの整数
long 8 4 大きめの整数(OS で違う)
long long 8 8 大きな整数
double 8 8 小数
long double 16 double と同じ 精度の高い小数
ポインタ(int * など) 8 ― アドレス

Windows の値は Microsoft Learn の「データ型の範囲」の表です。long は Linux で8バイト、Windows で4バイトと違います。 大きさを決めたいときは <stdint.h> の int32_t・int64_t を使います。2進数・16進数での表し方は、関連記事「16進数とは|変換・計算のやり方と0〜255早見表を3言語で実演」で扱っています。

計算・if・for の基本

int a = 7, b = 2;
printf("7 / 2 = %d\n", a / b);
printf("7 %% 2 = %d\n", a % b);
printf("(double)7 / 2 = %.1f\n", (double)a / b);

unsigned char u = 255;
u = u + 1;
printf("unsigned char 255 + 1 = %d\n", u);

char c = 'A';
printf("'A' は %%c で %c、%%d で %d\n", c, c);

int sum = 0;
for (int i = 1; i <= 10; i++) {
    sum += i;
}
printf("1〜10 の合計 = %d\n", sum);
7 / 2 = 3
7 % 2 = 1
(double)7 / 2 = 3.5
unsigned char 255 + 1 = 0
'A' は %c で A、%d で 65
1〜10 の合計 = 55
  • 整数どうしの割り算は小数点以下が切り捨て(7 / 2 = 3)。小数がほしいときは (double) で型を変えてから割る。
  • 符号なし(unsigned)の型はあふれると0に戻る(255 + 1 = 0)。符号ありの int のあふれは、前の節のとおり未定義動作。
  • char の中身は数。'A' は文字コードの 65。

printf の書式(よく使うもの)

書式 型 例
%d int printf("%d", 42) → 42
%ld・%lld long・long long 大きな整数
%u unsigned int 符号なし
%f・%.2f double 3.500000・3.50
%c 1文字 A
%s 文字列(char *) hello
%p ポインタ(アドレス) 0x7fff0c1e5efc
%zu size_t(sizeof の結果) 4
%x 16進数 ff
%% % そのもの %

よく使う標準ライブラリのヘッダー

C言語の言語そのものは小さく、入出力や文字列の処理は標準ライブラリの関数を #include して使います。

ヘッダー 主な中身 この記事で使った例
<stdio.h> 入出力 printf・fprintf・snprintf・scanf
<stdlib.h> メモリ・変換・終了 malloc・free・realloc・atoi
<string.h> 文字列・メモリの操作 strlen・strcpy・strcmp
<math.h> 数学関数(リンクに -lm) sqrt
<stdint.h> 大きさの決まった整数型 int32_t
<stdbool.h> bool・true・false bool
<limits.h>・<float.h> 型の最大値・最小値 INT_MAX・DBL_DIG
<stddef.h> size_t・NULL・offsetof offsetof

ネットワーク(<sys/socket.h>)などは OS が用意するヘッダーで、Linux・Mac と Windows で中身が違います。テストには Unity・CppUTest などの C 向けのテスト用ライブラリが使われます。

配列と文字列

この節では、配列がメモリにどう並ぶかと、C言語の文字列の正体が分かります。

配列はメモリに隙間なく並ぶ

要素のアドレスを表示すると、int(4バイト)ずつ番地が増えていました。

int nums[4] = {10, 20, 30, 40};
int *p = nums;                         /* 配列名は先頭要素のアドレスになる */

for (int i = 0; i < 4; i++) {
    printf("nums[%d] = %d  アドレス %p  *(p+%d) = %d\n",
           i, nums[i], (void *)&nums[i], i, *(p + i));
}
printf("sizeof(nums) = %zu、要素数 = %zu\n", sizeof(nums), sizeof(nums) / sizeof(nums[0]));
nums[0] = 10  アドレス 0x7fff74ff3430  *(p+0) = 10
nums[1] = 20  アドレス 0x7fff74ff3434  *(p+1) = 20
nums[2] = 30  アドレス 0x7fff74ff3438  *(p+2) = 30
nums[3] = 40  アドレス 0x7fff74ff343c  *(p+3) = 40
sizeof(nums) = 16、要素数 = 4

nums[i] と *(p + i) は同じ要素を指します。 p + 1 は「1バイト先」ではなく「int 1個分(4バイト)先」です。要素数は sizeof(配列) / sizeof(要素) で求めます(関数に渡した後は使えません。関数の節で実測)。

文字列は「最後が \0 の char の配列」

C言語には文字列の型がありません。文字列は char の配列で、最後に '\0'(値が0の文字)が付きます。 "hello" は5文字ですが、配列の大きさは6バイトでした。

char s[] = "hello";
printf("s = %s  strlen = %zu  sizeof = %zu\n", s, strlen(s), sizeof(s));
s = hello  strlen = 5  sizeof = 6
s[0] = 'h' (104)
s[1] = 'e' (101)
s[2] = 'l' (108)
s[3] = 'l' (108)
s[4] = 'o' (111)
s[5] = '\0' (0)
"あ" の strlen = 3、sizeof = 4

strlen は '\0' の手前までの「バイト数」を数えます。UTF-8 で保存したソースでは、日本語の「あ」は3バイトなので、strlen は3でした。文字数ではない点に気を付けます。

やりたいこと 関数(<string.h>) 注意
長さ(バイト数) strlen(s) '\0' は数えない
コピー strcpy(dst, src) dst の大きさを検査しない
つなげる strcat(dst, src) 同上
比べる strcmp(a, b) 同じなら 0。== では比べられない
大きさを守って書く snprintf(buf, sizeof(buf), ...) はみ出す分は切り捨てる
buf = abcdef、strcmp("abc","abd") = -1
snprintf の結果 = item-42

strcpy は書き込み先の大きさを確かめません。8バイトの配列に19バイトをコピーすると、後の節の stack smashing detected で止まります。大きさが決まっている場所に書くときは snprintf を使うと、はみ出しを防げます。

ポインタとは(アドレスを表示して理解する)

この節では、ポインタが何を入れる変数かを、実際のアドレスを表示して確かめます。

ポインタは「メモリのアドレス(番地)」を入れる変数です。 &x で変数 x のアドレスを取り出し、*p で p が指す先の値を読み書きします。

int x = 10;
int *p = &x;              /* p に x のアドレスを入れる */

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

*p = 20;                  /* p を通して x を書き換える */
printf("*p = 20 の後の x: %d\n", x);
x の値        : 10
x のアドレス  : 0x7fff0c1e5efc
p の値        : 0x7fff0c1e5efc
p が指す値 *p : 10
p 自身のアドレス: 0x7fff0c1e5f00
*p = 20 の後の x: 20

p の値は x のアドレスと同じ 0x7fff0c1e5efc でした。 p を通して 20 を書くと、x が 20 に変わりました。図にすると次のとおりです。

flowchart TD
    P[変数 p<br>場所 0x7fff0c1e5f00<br>中身 0x7fff0c1e5efc] -->|指す| X[変数 x<br>場所 0x7fff0c1e5efc<br>中身 10]
    S[*p = 20] -->|p を通して書く| X
書き方 意味 この例の値
int *p; int を指すポインタ p を宣言する ―
&x x のアドレス 0x7fff0c1e5efc
p p に入っているアドレス 0x7fff0c1e5efc
*p p が指す先の値 10
*p = 20; p が指す先に書く x が 20 になる
p + 1 次の要素のアドレス 4バイト先(int の場合)
NULL どこも指していない印 読むと Segmentation fault

もう一度実行すると、アドレスは 0x7ffcad50d8ac に変わりました。Linux などの OS は、攻撃を難しくするために、実行のたびにメモリの配置を変えています(ASLR)。アドレスの値そのものではなく、「p と &x が同じ」という関係を見ます。

プログラムのメモリの配置

関数・文字列・グローバル変数・malloc の領域・ローカル変数のアドレスを並べると、置き場所が分かれていました。

関数 main      : 0x55a71764c1a9
文字列リテラル : 0x55a71764d004
グローバル変数 : 0x55a71764f01c
ヒープ(malloc): 0x55a754e552a0
ローカル変数   : 0x7fffbe6f45ac
flowchart TD
    A[コード・文字列リテラル<br>0x55a71764c1a9 付近] --> B[グローバル変数<br>0x55a71764f01c]
    B --> C[ヒープ<br>malloc で確保<br>0x55a754e552a0]
    C --> D[空き]
    D --> E[スタック<br>ローカル変数<br>0x7fffbe6f45ac]
領域 置かれるもの いつ消えるか
コード 関数の機械語、文字列リテラル プログラムの終了まで(書き換え不可)
静的領域 グローバル変数・static 変数 プログラムの終了まで
ヒープ malloc で確保したもの free したとき
スタック ローカル変数・関数の引数 関数を抜けたとき

Java の変数がどこに置かれるかとの比較は、関連記事「Java ヒープとスタックの違い|変数の保存場所とメタスペースを図解」で、Go のポインタの書き方は関連記事「Goのポインタの使い方|*と&の違い、値渡しとポインタ渡し、レシーバ」で扱っています。

関数(値渡しとポインタ渡し)

この節では、関数の書き方と、引数がコピーされることによる落とし穴が分かります。

C言語の関数の引数は「値のコピー」で渡されます。 呼び出した側の変数を書き換えたいときは、アドレス(ポインタ)を渡します。

#include <stdio.h>

int add(int a, int b);                 /* プロトタイプ宣言 */
void swap_value(int a, int b);
void swap_pointer(int *a, int *b);
int sum_array(int arr[], int n);

int main(void)
{
    printf("add(3, 4) = %d\n", add(3, 4));

    int x = 1, y = 2;
    swap_value(x, y);
    printf("swap_value の後  : x=%d y=%d\n", x, y);
    swap_pointer(&x, &y);
    printf("swap_pointer の後: x=%d y=%d\n", x, y);

    int nums[5] = {1, 2, 3, 4, 5};
    printf("main での sizeof(nums) = %zu\n", sizeof(nums));
    printf("sum_array = %d\n", sum_array(nums, 5));
    return 0;
}

int add(int a, int b)
{
    return a + b;
}

void swap_value(int a, int b)          /* 値のコピーを受け取る */
{
    int t = a; a = b; b = t;
}

void swap_pointer(int *a, int *b)      /* アドレスを受け取る */
{
    int t = *a; *a = *b; *b = t;
}

int sum_array(int arr[], int n)
{
    printf("関数の中での sizeof(arr) = %zu\n", sizeof(arr));
    int s = 0;
    for (int i = 0; i < n; i++) s += arr[i];
    return s;
}
add(3, 4) = 7
swap_value の後  : x=1 y=2
swap_pointer の後: x=2 y=1
main での sizeof(nums) = 20
関数の中での sizeof(arr) = 8
sum_array = 15

x と y を入れ替える関数

swap_value(x, y)x=1 y=2 のまま(コピーを入れ替えただけ)
→
swap_pointer(&x, &y)x=2 y=1 に入れ替わる

関数の外の変数を変えたいときは、アドレスを渡して *a で書き換える

配列を関数に渡すと、先頭のアドレス(ポインタ)として渡ります。 main では 20 バイトだった sizeof が、関数の中では 8(ポインタの大きさ)になりました。gcc もコンパイルのときに警告を出していました。そのため、配列を渡す関数には要素数を別の引数で渡します。

func.c:41:54: warning: 'sizeof' on array function parameter 'arr' will return size of 'int *' [-Wsizeof-array-argument]

関数を main より後ろに書くときは、先頭にプロトタイプ宣言(int add(int a, int b);)を書きます。宣言が無いと、GCC 14 以降ではコンパイルエラーになります(エラーの節)。

構造体(struct)

この節では、複数の値をまとめる構造体の書き方と、メモリ上の並び方が分かります。

構造体は、型の違う値を1つにまとめた型です。 Java や Python のクラスから、メソッドを除いたものに近い形です。

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

typedef struct {
    char name[16];
    int  age;
    double height;
} Person;

struct Mixed {
    char c;
    int  i;
};

void birthday(Person *p)
{
    p->age++;                     /* (*p).age++ と同じ */
}

int main(void)
{
    Person taro = {"Taro", 20, 171.5};
    Person copy = taro;           /* 構造体は代入でまるごとコピーされる */
    birthday(&taro);
    strcpy(copy.name, "Copy");

    printf("%s: %d 歳, %.1f cm\n", taro.name, taro.age, taro.height);
    printf("%s: %d 歳\n", copy.name, copy.age);
    printf("sizeof(Person) = %zu\n", sizeof(Person));
    printf("offsetof name=%zu age=%zu height=%zu\n",
           offsetof(Person, name), offsetof(Person, age), offsetof(Person, height));
    printf("sizeof(struct Mixed) = %zu (char 1 + int 4 なのに)\n", sizeof(struct Mixed));
    printf("offsetof c=%zu i=%zu\n", offsetof(struct Mixed, c), offsetof(struct Mixed, i));
    return 0;
}
Taro: 21 歳, 171.5 cm
Copy: 20 歳
sizeof(Person) = 32
offsetof name=0 age=16 height=24
sizeof(struct Mixed) = 8 (char 1 + int 4 なのに)
offsetof c=0 i=4
書き方 意味
taro.age 構造体の変数からメンバーを読む
p->age 構造体へのポインタからメンバーを読む((*p).age と同じ)
Person copy = taro; 全メンバーをコピーした別の変数ができる
typedef struct {...} Person; struct を付けずに Person と書けるようにする

char 1バイトと int 4バイトを並べた構造体(struct Mixed)は、合計5バイトではなく8バイトでした。offsetof で見ると、int は4バイト目から始まっています。CPU が読みやすい位置にそろえるため、コンパイラが間に隙間(パディング)を入れるためです。ファイルや通信でバイト列をそのまま読み書きするときは、この隙間に気を付けます。

malloc・freeでメモリを確保する

この節では、実行中に必要な分だけメモリを確保して、返す書き方が分かります。

malloc はヒープからメモリを確保し、free で返します。 配列の大きさが実行してから決まるときに使います。

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

int main(void)
{
    int n = 5;
    int *arr = malloc(n * sizeof(int));      /* int 5 個分を確保 */
    if (arr == NULL) {
        fprintf(stderr, "メモリを確保できませんでした\n");
        return 1;
    }
    for (int i = 0; i < n; i++) arr[i] = (i + 1) * 10;

    int *tmp = realloc(arr, 10 * sizeof(int)); /* 10 個に広げる */
    if (tmp == NULL) {
        free(arr);
        return 1;
    }
    arr = tmp;
    for (int i = n; i < 10; i++) arr[i] = (i + 1) * 10;

    for (int i = 0; i < 10; i++) printf("%d ", arr[i]);
    printf("\n");

    free(arr);       /* 使い終わったら返す */
    arr = NULL;      /* 返した後に使わないよう NULL にしておく */
    return 0;
}
10 20 30 40 50 60 70 80 90 100

Valgrind で確かめると、確保と解放の数がそろい、漏れ(リーク)はありませんでした。

==1525== HEAP SUMMARY:
==1525==     in use at exit: 0 bytes in 0 blocks
==1525==   total heap usage: 3 allocs, 3 frees, 4,156 bytes allocated
==1525==
==1525== All heap blocks were freed -- no leaks are possible

(3 allocs のうち1つは、printf が内部で使う領域です。)

関数 すること 失敗したとき
malloc(バイト数) 確保する(中身は不定) NULL を返す
calloc(個数, 1個の大きさ) 確保して0で埋める NULL を返す
realloc(ポインタ, 新しいバイト数) 広げる・縮める(場所が変わることがある) NULL を返し、元の領域は残る
free(ポインタ) 返す ― (2回呼ぶと異常終了)
  • 1malloc の戻り値が NULL でないか確かめた確保に失敗すると NULL が返る
  • 2大きさを「個数 × sizeof(型)」で書いたmalloc(5) は5バイト。int 5個なら 5 * sizeof(int)
  • 3realloc の結果を別の変数で受けた失敗して NULL が返ると、元のアドレスを失ってリークする
  • 4確保したものを1回ずつ free した忘れるとリーク、2回だと double free
  • 5free の後はポインタを NULL にした解放後の領域を使う誤り(use after free)を防ぐ

よくあるコンパイルエラーと直し方

この節では、gcc が出すエラー・警告の原文と、何を直せばよいかが分かります。すべて GCC 13.3.0 で実際に出たものです。

文法のエラー(コンパイルが止まる)

セミコロンが抜けたときと、宣言していない変数を使ったときです。

e1.c: In function 'main':
e1.c:5:22: error: expected ';' before 'return'
    5 |     printf("Hello\n")
      |                      ^
      |                      ;
    6 |     return 0;
      |     ~~~~~~

e2.c: In function 'main':
e2.c:5:5: error: 'count' undeclared (first use in this function)
    5 |     count = 1;
      |     ^~~~~

e1.c:5:22 は「5行目の22文字目」です。セミコロンが無いエラーは、次の行(return)の手前として報告されます。

リンクのエラー(undefined reference)

コンパイルは通ったのに、部品をつなぐ段階で見つからないときのエラーです。最後の collect2: error: ld returned 1 exit status が目印です。

$ gcc e4.c -o e4
e4.c:5:5: warning: implicit declaration of function 'prinf'; did you mean 'printf'? [-Wimplicit-function-declaration]
/usr/bin/ld: /tmp/cciJO71s.o: in function `main':
e4.c:(.text+0x18): undefined reference to `prinf'
collect2: error: ld returned 1 exit status

$ gcc e5.c -o e5
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x1b): undefined reference to `main'
collect2: error: ld returned 1 exit status

$ gcc e6.c -o e6
/usr/bin/ld: /tmp/ccgfGn69.o: in function `main':
e6.c:(.text+0x2f): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status
$ gcc e6.c -o e6 -lm
$ ./e6
1.414214

-Wall で初めて出る警告

次のコードは、-Wall を付けないと2件しか警告が出ませんでした。-Wall -Wextra を付けると6件出ました。

double price = 120.5;
int n;
char name[8] = "abc";
printf("%d\n", price);
printf("%d\n", n);
if (n = 3) { printf("3\n"); }
if (name == "abc") { printf("same\n"); }
int age;
scanf("%d", age);
e7.c:9:14: warning: format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat=]
e7.c:11:9: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
e7.c:14:14: warning: comparison with string literal results in unspecified behavior [-Waddress]
e7.c:18:13: warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int' [-Wformat=]
e7.c:10:5: warning: 'n' is used uninitialized [-Wuninitialized]
e7.c:18:5: warning: 'age' is used uninitialized [-Wuninitialized]

警告でも実行ファイルはできます。 できたからといって正しく動くとは限らないので、警告が0件になるまで直します。

エラー・警告の原文と直し方の一覧

エラー・警告(原文) 原因 直し方
error: expected ';' before 'return' 前の行の ; が無い 指された位置の前の行末に ;
error: 'count' undeclared (first use in this function) 宣言していない変数・打ち間違い int count = 0; のように宣言する
warning: implicit declaration of function 'printf' #include <stdio.h> が無い 注記どおり #include を足す
undefined reference to `prinf' 関数名の打ち間違い・本体が無い 名前を直す。別ファイルなら一緒にリンク
undefined reference to `main' main が無い(mian など) int main(void) を書く
undefined reference to `sqrt' 数学ライブラリをつないでいない 最後に -lm を付ける
format '%d' expects argument of type 'int', but argument 2 has type 'double' 書式と型が合わない double は %f
format '%d' expects argument of type 'int *' scanf に & が無い scanf("%d", &age)
suggest parentheses around assignment used as truth value if (n = 3) は代入 比較は ==
comparison with string literal results in unspecified behavior 文字列を == で比べた strcmp(name, "abc") == 0
'n' is used uninitialized 値を入れずに使った 宣言のときに初期化する
makes integer from pointer without a cast int n = "123"; など型の取り違え 数にするなら atoi・strtol
'sizeof' on array function parameter 関数の中で配列の sizeof 要素数を引数で渡す
fatal error: nothing.c: No such file or directory ファイル名・場所の誤り ls で名前と今いるフォルダを確かめる
gcc: fatal error: no input files ソースファイルを指定していない gcc ファイル名.c

GCC 14 以降で警告からエラーに変わったもの

GCC 14 の移行ガイドには、C で次の診断が警告からエラーに変わったと書かれています。GCC 13 では警告で済んでいたコードが、新しい gcc ではコンパイルできなくなります。

診断 例 GCC 13(実測)
宣言していない関数の呼び出し(implicit-function-declaration) #include 忘れ・打ち間違い 警告
型を書かない宣言(implicit-int) main() の戻り値の型なし 警告
整数とポインタの取り違え(int-conversion) int n = "123"; 警告
型の違うポインタの代入(incompatible-pointer-types) int * に char * 警告
void の関数で値を返す(return-mismatch) return 1; 警告

Clang 18 では、宣言していない関数の呼び出しは既にエラーでした(error: call to undeclared function 'prinf'; ISO C99 and later do not support implicit function declarations)。どのコンパイラでも通るように、警告の段階で直しておきます。

Segmentation faultの原因と直し方

この節では、実行中にプログラムが落ちる「Segmentation fault」が何で、どこで起きたかを調べる方法が分かります。

Segmentation fault(セグメンテーション違反)は、使ってはいけないメモリを読み書きしたため、OS がプログラムを止めたことを表します。 終了コードは 139 でした。

int *p = NULL;
printf("これから NULL を読みます\n");
printf("%d\n", *p);
$ bash -c './segv; echo "exit=$?"'
これから NULL を読みます
bash: line 1:  5633 Segmentation fault      ./segv
exit=139

出力をファイルやパイプに流したときは、これから NULL を読みます も表示されませんでした(./segv > out.txt の out.txt は0バイト)。端末以外への出力はためてから書かれる(バッファリング)ため、書く前に落ちると消えます。どこまで進んだかを printf で探すときは、fprintf(stderr, ...) を使うと消えません。

よくある原因

原因 例 直し方
NULL を読み書きした int *p = NULL; *p 使う前に if (p != NULL)
文字列リテラルを書き換えた char *s = "abc"; s[0] = 'X'; char s[] = "abc"; と配列にする
配列の範囲を大きく超えた arr[100000] ループの条件を i < n にする
free した後に使った free(p); *p free 後は p = NULL
再帰が止まらない 終わる条件の無い再帰 終わる条件を書く
scanf に & が無い scanf("%d", age) &age

配列からはみ出して書いた:stack smashing detected

8バイトの配列 name に、19バイト(18文字+'\0')を strcpy でコピーしました。gcc はコンパイルのときに警告を出し、実行すると、表示の後に異常終了しました(終了コード 134)。

smash.c:7:5: warning: '__builtin_memcpy' writing 19 bytes into a region of size 8 overflows the destination [-Wstringop-overflow=]
$ bash -c './smash; echo "exit=$?"'
Yamada Taro Hanako
*** stack smashing detected ***: terminated
bash: line 1:  5636 Aborted                 ./smash
exit=134

関数の終わりに、スタックの見張りの値(カナリア)が書き換えられていないかを確かめる仕組みが働き、プログラムを止めました。コピー先の大きさを超えない snprintf(name, sizeof(name), "%s", 元の文字列) に変えると、はみ出しは起きません。

文字列リテラルを書き換えた

文字列リテラルの書き換えも実行しました。配列にコピーした t は書き換えられ、リテラルを指す s に書いたところで落ちました。

t = Xbc
bash: line 1: 12174 Segmentation fault      ./lit

gdb で落ちた行を見つける

-g(デバッグ情報を入れる)を付けてコンパイルし、gdb で実行すると、落ちた行が表示されます。

$ gcc -g segv.c -o segv
$ gdb -q -batch -ex run -ex bt ./segv
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555190 in main () at segv.c:7
7       printf("%d\n", *p);
#0  0x0000555555555190 in main () at segv.c:7

メモリの誤りをvalgrind・AddressSanitizerで見つける

この節では、目に見えないメモリの誤りを、道具で見つける方法と実際の出力が分かります。

メモリの誤りの多くは、その場では落ちずに進みます。 4種類の誤りを入れたプログラムを、何も付けずに実行した結果です。

誤り 何も付けずに実行 valgrind -fsanitize=address
解放し忘れ(リーク) 正常終了 definitely lost: 40 bytes detected memory leaks
確保した範囲の外に書く 99 と表示(正常終了) Invalid write of size 4 heap-buffer-overflow
free の後に使う 1510646364 と表示 Invalid read of size 4 heap-use-after-free
2回 free する free(): double free detected in tcache 2(終了コード 134) Invalid free() attempting double-free

valgrind

コンパイルし直さずに使えます。Valgrind のマニュアルは、-g を付け、-O0 でコンパイルすることを勧めています。範囲外に書いた例では、確保した20バイトの「0バイト後ろ」に書いたことと、その行(14行目)が出ました。

$ gcc -g -O0 bugs.c -o bugs
$ valgrind --leak-check=full ./bugs 2
==1624== Invalid write of size 4
==1624==    at 0x10923A: main (bugs.c:14)
==1624==  Address 0x4a7e054 is 0 bytes after a block of size 20 alloc'd
==1624==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1624==    by 0x10922D: main (bugs.c:13)

解放し忘れの例では、9行目で確保した40バイトが返されていないと出ました。

==1616== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==1616==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1616==    by 0x1091EE: main (bugs.c:9)

AddressSanitizer(-fsanitize=address)

gcc・clang に入っている検出の仕組みで、コンパイルのときにオプションを付けます。誤りが見つかると、その場で止まり、終了コードは 1 でした。

$ gcc -g -O0 -fsanitize=address -fno-omit-frame-pointer bugs.c -o bugs_asan
$ ./bugs_asan 3
==1679==ERROR: AddressSanitizer: heap-use-after-free on address 0x502000000010 at pc 0x5591546604b6 bp 0x7ffcc9825a20 sp 0x7ffcc9825a10
READ of size 4 at 0x502000000010 thread T0
    #0 0x5591546604b5 in main bugs.c:21
    (中略)
freed by thread T0 here:
    #0 0x7fce6f6fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x55915466047e in main bugs.c:20
    (中略)
previously allocated by thread T0 here:
    #0 0x7fce6f6fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x55915466042d in main bugs.c:18

「21行目で読んだ領域は、20行目で free され、18行目で確保されたもの」と、3つの行が一度に分かります。

道具 使い方 速さ(Clang・Valgrind の文書) 向いている場面
AddressSanitizer -fsanitize=address でコンパイルし直す 典型で約2倍遅い ふだんのテスト・CI
UndefinedBehaviorSanitizer -fsanitize=undefined ― 整数のあふれ・範囲外の添字
valgrind valgrind --leak-check=full ./prog 20〜30倍遅いことがある コンパイルし直せないとき
gdb gdb ./prog ― 落ちた行・変数の値を見る

この記事の素数のプログラムでは、-O1 で 0.14 秒、AddressSanitizer 付きも 0.14 秒、valgrind では 1.43 秒でした。メモリを手で管理しない言語のしくみとしては、Rust の所有権があります。関連記事「Rust入門|所有権からCLI作成までコピペで動くコード付き」で扱っています。

C言語の学習の順番

この節では、入門からどの順で学ぶと進めやすいかが分かります。

本や学習サイトで文法を読み、手元の gcc で1つずつ動かして出力を確かめながら進めます。

1

環境を作り Hello World を動かす

コンパイラを入れ、gcc -Wall -Wextra でコンパイルする習慣を付けます。

環境構築・Hello World の節

2

変数・型・if・for

整数の割り算の切り捨て、printf の書式と型の対応を押さえます。

変数と型の節

3

配列と文字列

文字列は最後に '\0' がある char の配列、と理解します。

配列と文字列の節

4

関数

引数はコピーで渡る。配列は先頭のアドレスで渡る。

関数の節

5

ポインタ

& と * を、アドレスを printf しながら確かめます。

いちばんつまずきやすい所

6

構造体と malloc・free

動的な配列や、構造体をつないだリストを作ります。

valgrind か ASan で確かめる

7

分割コンパイルと道具

ヘッダーファイル、Makefile、gdb を使って、複数のファイルのプログラムを作ります。

コンパイルの仕組みの節

よくある質問

Q. C言語とは何ですか? A. 1972年にベル研究所のデニス・リッチーが作った、コンパイルして機械語で実行するプログラミング言語です。OS・組み込み機器・データベースなど、速さとハードウェアに近い操作が要る所で使われています。

Q. C言語の読み方は? A. 「シーげんご」です。英語では C(シー)と呼びます。

Q. C言語で何ができますか? A. OS(Linux カーネル)、組み込み機器の制御、データベース(SQLite・PostgreSQL)、Web サーバー(nginx)、他の言語から呼ぶライブラリなどが作られています。「C言語でできること・使われている場所」の節に表があります。

Q. C言語は難しいですか? A. 文法は小さい言語ですが、ポインタとメモリの管理を自分で行う点が難しいと言われます。誤りがエラーにならずに進むことがあるので、-Wall -Wextra と -fsanitize=address を付けて確かめながら学ぶと、つまずきを見つけやすくなります。

Q. C言語とC++の違いは? A. C++ は C を拡張して作られた言語で、クラスなどのオブジェクト指向の機能が加わっています。C のコードの多くは C++ としてもコンパイルできます。C# は名前が似ていますが別系統の言語で、GC でメモリを自動で管理します。

Q. C言語を始めるには何が必要ですか? A. コンパイラです。Windows は Visual Studio の「C++ によるデスクトップ開発」か MSYS2 の gcc、Mac は xcode-select --install で入る clang、Linux は sudo apt install build-essential で入る gcc を使います。

Q. コンパイルとは何ですか? A. 人が書いたソースコードを、CPU が実行できる機械語のファイルに変えることです。gcc は前処理(-E)・コンパイル(-S)・アセンブル(-c)・リンクの4段階で行います。

Q. ポインタとは何ですか? A. メモリのアドレス(番地)を入れる変数です。&x で x のアドレスを取り、*p で指す先の値を読み書きします。この記事の実行例では、p の値と x のアドレスが同じ 0x7fff0c1e5efc でした。

Q. Segmentation fault とは何ですか? A. 使ってはいけないメモリ(NULL・解放済み・範囲外・書き換え不可の領域)を読み書きしたため、OS がプログラムを止めたことです。gcc -g でコンパイルして gdb で実行すると、落ちた行が分かります。

Q. C言語の最新の規格は? A. 2026年10月22日時点で、ISO/IEC 9899:2024(通称 C23)です。GCC 15 から、gcc の既定は gnu23 になりました。

Q. C言語は今でも使われていますか? A. 使われています。Linux カーネルは C で書かれており、2026年10月の TIOBE Index では C は2位でした。新しいシステムの一部では Rust も使われ始めています(Linux カーネルも Rust に対応)。

Q. printf と puts の違いは? A. printf は %d などの書式で値を埋め込めます。puts は文字列をそのまま出し、最後に改行を付けます。書式の無い printf("...\n") は、gcc が puts に置き換えることがあります(コンパイルの仕組みの節で確認)。

まとめ

  • C言語は1972年生まれのコンパイル型の言語。OS・組み込み・データベースなどで使われ、最新の規格は C23(ISO/IEC 9899:2024)。
  • gcc は前処理(-E)・コンパイル(-S)・アセンブル(-c)・リンクの4段階で実行ファイルを作る。7行のソースは前処理で820行になった。
  • 型の大きさは環境で変わる。64ビット Linux の int は4バイト、long は8バイト(Windows は4バイト)。
  • 文字列は最後に '\0' がある char の配列。ポインタはアドレスを入れる変数で、& で取り、* で指す先を読み書きする。
  • 関数の引数はコピーで渡る。呼び出し元を変えたいときはアドレスを渡す。配列は先頭のアドレスで渡る。
  • malloc で確保したら NULL を確かめ、1回だけ free する。
  • 警告は -Wall -Wextra で出し、0件にする。メモリの誤りは -fsanitize=address か valgrind で見つける。

参考・出典

確認日はいずれも 2026年10月22日です。

  • ISO「ISO/IEC 9899:2024 Information technology — Programming languages — C」:https://www.iso.org/standard/82075.html
  • GCC 公式マニュアル「Options Controlling the Kind of Output」:https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html
  • GCC 15 Release Series「Changes, New Features, and Fixes」:https://gcc.gnu.org/gcc-15/changes.html
  • GCC 14「Porting to GCC 14」:https://gcc.gnu.org/gcc-14/porting_to.html
  • Microsoft Learn「チュートリアル: コマンド ラインでの C プログラムのコンパイル」:https://learn.microsoft.com/ja-jp/cpp/build/walkthrough-compile-a-c-program-on-the-command-line
  • Microsoft Learn「データ型の範囲」:https://learn.microsoft.com/ja-jp/cpp/cpp/data-type-ranges
  • Visual Studio Code「Using GCC with MinGW」:https://code.visualstudio.com/docs/cpp/config-mingw
  • Apple Developer「Installing the command-line tools」:https://developer.apple.com/documentation/xcode/installing-the-command-line-tools
  • Clang「AddressSanitizer」:https://clang.llvm.org/docs/AddressSanitizer.html
  • Valgrind「The Valgrind Quick Start Guide」:https://valgrind.org/docs/manual/quick-start.html
  • The Linux Kernel documentation「Programming Language」:https://docs.kernel.org/process/programming-language.html
  • SQLite:https://sqlite.org/index.html
  • Python ドキュメント「Extending Python with C or C++」:https://docs.python.org/3/extending/extending.html
  • TIOBE Index:https://www.tiobe.com/tiobe-index/
  • Wikipedia「C言語」:https://ja.wikipedia.org/wiki/C言語
  • IT用語辞典 e-Words「C言語」:https://e-words.jp/w/C言語.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「C言語とは|入門から環境構築・ポインタ・mallocまで実行例で解説」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?