C言語入門

トップ > C言語入門

目次

C言語とは

Hello world

「The C Programming Language」で紹介された Hello world は下記の様になります。プログラムは必ず main() という名前の関数から始まります。K&R に敬意を表して「hello, world!」は上記書籍の記法にしています(h は小文字で、hello と world の間にカンマ)。

#include <stdio.h>

main() {
    printf("hello, world!\n");
}

プログラムの拡張子は *.c です。コンパイルは gcc コマンドで行います。

$ gcc -o hello hello.c
$ ./hello
hello, world!

printf()

printf(format, arg1, arg2, ...)format の書式に従い、arg1arg2 などの値を書き出します。%s は文字列の値に置き換えられます。%d は整数の値に置き換えられます。\n は改行を意味します。

char *name = "Yamada";
int age = 32;
printf("My name is %s. I'm %d years old.\n", name, age);

%s%d の他には次のようなものがあります。

%c		// 文字(例:'a')
%s		// 文字列(例: "abc")
%d		// 符号付き整数(例: -123)
%u		// 符号無し整数(例: 123)
%o		// 8進数(例: 0755)
%x		// 16進数(例: 0x89ab)
%f		// 浮動小数点数(非指数表示)(例: 123.4)
%e		// 浮動小数点数(指数表示)(例: 1.234e+2)
%g		// 浮動小数点数(%fか%eを自動選択)(例: 123.4)
%%		// % 自体を表示

% の後ろに数値をつけると桁数・文字数を示します。

int n = 123;
float x = 12.3;
char *s = "ABC";
printf("%5d", n);		// 右詰め5桁の数値として表示(  123)
printf("%-5d", n);		// 左詰め5桁の数値として表示(123  )
printf("%05d", n);		// 0埋め5桁の数値として表示(00123)
printf("%5.2f", x);		// 小数点以下2桁、全体5桁の実数として表示(12.30)
printf("%5s", s);		// 右寄せ5文字の文字列として表示(  ABC)
printf("%-5s", s);		// 左寄せ5文字の文字列として表示(ABC  )

コメント(/* */, //)

コメントは /**/ で挟んで記述します。

/* ここはコメント */

/*
 * 複数行にまたがるコメントも
 * 記述できます
 */

C99 では // によるコメントもサポートされました。

// 行末までがコメントになります

キーワード

キーワードには以下のものがあります。

auto		break		case		char
const		continue	default		do
double		else		enum		extern
float		for		goto		if
inline		int		long		register
restrict	return		short		signed
sizeof		static		struct		switch
typedef		typeof		union		unsigned
void		volatile	while		_Alignas
_Alignof	_Atomic		_Bool		_Complex
_Generic	_Imaginary	_Noreturn	_Static_assert
_Thread_local

整数(char・short・int・long・long long)

char は8ビット、short は16ビット整数を示します。int はシステムが最も扱いやすいビット長の整数で、16ビットマシンでは 16ビット、32ビットマシンでは 32ビットでしたが、64ビットマシンでは互換性を考慮して 32ビットのままとするシステムが多いようです。longint と同じかそれ以上のビット数の整数、long longlong と同じかそれ以上のビット数の整数を示します。

char			// 符号あり8ビット整数(-128~127)
short			// 符号あり16ビット整数(-32768~32767)
int			// 符号あり整数(通常32ビット)
long			// 符号あり整数(通常32~64ビット)
long long		// 符号あり整数(通常64ビット)
符号(signed・unsigned)

signed をつけると符号あり、unsigned をつけると符号なし、省略すると符号ありになります。

signed char		// 符号あり8ビット整数(-128~127)
unsigned char		// 符号なし8ビット整数(0~255)
実数(float・double)

float は単精度、double は倍精度の浮動小数点数を扱います。

float			// 単精度浮動小数点数
double			// 倍精度浮動小数点数
void型(void)

void は値が無いことを示す特別な型です。

void			// 値無しを示す特別な型

型定義(typedef)

typedef は既存の型に別名をつけます。下記の例では unsigned int という型に uint という別名をつけ、それを使用しています。

typedef unsigned int uint;

main() {
    uint n = 123;
    :

下記の例では struct dnode という型に DNODE という別名をつけています。

typedef struct dnode {
    int id;
    char *data;
} DNODE;

main() {
    DNODE d;
    :

キャスト

int型の変数の値を long型の変数に代入したりする場合は、暗黙的な型の変換が行われます。

main() {
    int int_num = 123;
    long long_num;
    long_num = int_num;		// 暗黙的に int → long の型変換が行われる
    printf("%ld\n", long_num);
}

(型名) をつけることで、明示的に型変換することもできます。これを キャスト と呼びます。

main() {
    int int_num = 123;
    long long_num;
    long_num = (long)int_num;		// 明示的に int → long の型変換(キャスト)を行う
    printf("%ld\n", long_num);
}

変数・定数

変数

C言語ではあらかじめすべての変数を定義しておく必要があります。下記では int 型の変数 n を定義しています。

main() {
    int n;
}

変数には初期値を指定することができます。

main() {
    int n = 123;
}

関数外部で宣言したものを グローバル変数、関数内部で宣言したものを ローカル変数 と呼びます。グローバル変数はプログラムが終了するまで生存し、ファイル内すべての関数から参照できます。ローカル変数はメモリのスタック領域に確保され、関数内部でのみ参照され、関数終了時に消滅します。

int a1 = 123;		// グローバル変数

main() {
    int a2 = 123;	// ローカル変数
}
定数(const)

const を用いて定数を定義することができます。定数を変更しようとするとコンパイルエラーになります。

const float pi = 3.14;
静的変数(static)

static は静的変数を定義します。通常の変数は関数が終わるとスコープアウトして消滅しますが、static で宣言した変数はプログラムが終了するまで維持されます。

main() {
    int i;
    for (i = 0; i < 10; i++) {
        count();
    }
}

count() {
    static n = 0;
    n++;
    printf("%d\n", n);
}
レジスタ変数(register)

register は変数を レジスタ変数 で扱うようにコンパイラに依頼します。通常の変数はスタック領域に割り当てられますが、register を指定した変数は、可能であればレジスタに割り当てられます。これにより、よく使用する変数を高速に読み書きすることが可能となります。

register int n = 0;
自動変数(auto)

auto は自動変数を示します。自然変数は前述のローカル変数と同義です。これは古い言語との互換性のために残されたキーワードで、実際に使用されるケースはほとんどありません。C++ の auto とは別物です。

main() {
    auto int a1;		// 自然変数(=ローカル変数)
    int a2;			// ローカル変数(=自然変数)
}

配列

変数名[n] で配列を扱うことができます。配列のインデックスは 0 から始まります。a[3] とした場合、a[0]~a[2] の3個の要素を持つ配列を使用することができます。

main() {
    int a[3];
    a[0] = 123;
    a[1] = 456;
    a[2] = 789;
    printf("%d %d %d\n", a[1], a[2], a[3]);
}

配列の要素数を超えるインデックスを指定した場合でもコンパイラはエラーを検出しません。メモリが破壊され、以降のプログラムが予期しない動作をすることになります。

main() {
    int a[3];
    a[0] = 123;
    a[1] = 456;
    a[2] = 789;
    a[3] = 999;		// メモリ破壊
}

ポインタ

ポインタ は変数やデータが格納されたメモリ上のアドレス(番地)を意味します。変数のポインタを得るには「&変数名」を、ポインタの中身を参照するには 「*ポインタ」を使用します。「型名 *」は変数をポインタ変数として定義します。下記の例で、1つ目の * は a2 をポインタ変数として定義するもの、2つ目、3つ目の * は a2 ポインタの中身を参照するものです。

main() {
    int a1 = 123;		// int変数 a1 を確保し、その値として 123 を格納
    int *a2 = &a1;		// ポインタ変数 a2 を確保し、その値として a1 のポインタを代入
    printf("%d\n", *a2);	// a2 ポインタの中身(ポインタが示すメモリの中身)は 123
    *a2 = 321;			// a2 ポインタの中身(ポインタが示すメモリの中身)を 321 に変更
    printf("%d\n", a1);		// a1 の値が 321 に変わる
}

メモリ領域

メモリは、プログラム領域、スタティック領域、ヒープ領域、スタック領域に大別されます。プログラム領域にはプログラムが格納されます。スタティック領域はコンパイル時に決めることができる領域です。ヒープ領域は malloc(), free() などのメモリ関数で動的に確保・解放される領域です。スタック領域は関数の引数やローカル変数など、関数が呼び出される度に(通常下から)積み重なって確保される領域です。

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

void func(int *);
static int a2;				// スタティック領域

void main() {				// プログラム領域
    int a6;				// スタック領域
    func(&a6);
}

void func(int *a6) {			// プログラム領域
    char *a1 = "ABC";			// スタティック領域
    static int a3;			// スタティック領域
    char *a4 = malloc(4);		// ヒープ領域
    int a5;				// スタック領域

    printf("f1 = %08x\n", func);	// 関数名は関数のポインタを示す
    printf("f2 = %08x\n", main);	// 関数名は関数のポインタを示す
    printf("a1 = %08x\n", a1);		// a1はポインタ変数なのでそのまま表示
    printf("a2 = %08x\n", &a2);		// a2変数のポインタを表示
    printf("a3 = %08x\n", &a3);		// a3変数のポインタを表示
    printf("a4 = %08x\n", a4);		// a4はポインタ変数なのでそのまま表示
    printf("a5 = %08x\n", &a5);		// a5変数のポインタを表示
    printf("a6 = %08x\n", a6);		// a6はポインタ変数なのでそのまま表示
}

文字列

C言語には文字列という型は存在しないと言っても過言ではありません。char という 1バイト整数の配列を文字列とみなして処理します。0は文字列の終わりを意味します。

main() {
    char a[4];		// 4バイトの配列
    a[0] = 'A';		// 1バイト目に 'A'(0x41) を代入
    a[1] = 'B';		// 2バイト目に 'B'(0x42) を代入
    a[2] = 'C';		// 3バイト目に 'C'(0x43) を代入
    a[3] = 0;		// 4バイト目に  0 (0x00) を代入
    printf("%s\n", a);	// 0が見つかるまでを文字列とみなして印字する
}

下記の例ではメモリ上のスタティック領域に 0x41, 0x42, 0x43, 0x00 という4バイトの領域を確保し、そのポインタを a に代入しています。

main() {
    char *a = "ABC";
    printf("%s\n", a);
}

strcpy(), strcat(), strlen() などの文字列を扱う関数が用意されています。

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

void main() {
    char str[10];
    int len;
    strcpy(str, "ABC");		// 文字列をコピー(copy)する
    strcat(str, "DEF");		// 文字列を連結(concatenate)する
    len = strlen(str);
    printf("len=%d, str=%s\n", len, str); // => len=6, str=ABCDEF
}

strcpy(), strcat() はメモリ破壊を起こす可能性のある危険な関数のため、C11 では代わりにメモリサイズチェック機能を加えた strcpy_s(), strcat_s() を使用することを推奨しています。

strcpy_s(str, sizeof(str), "ABC");
strcat_s(str, sizeof(str), "DEF");

ただし、gcc では strcpy_s()strcat_s() をサポートしていません。類似の関数に strncpy()strncat() がありますが、第一引数のサイズ上限をチェックする訳ではない、文字列の最後の 0 を付加しないことがあるなど、非常にバグを埋め込みやすい関数ですので、迂闊には使用しないほうがよさそうです。

strncpy(str, "ABC", strlen(ABC));
strncat(str, "ABC", strlen(ABC));

動的に長さの変わる文字列を使用するには、malloc(), free() でヒープ領域に文字列を格納する必要があります。

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

// 文字列を生成する
char *create_string(char *s1) {
    char *s = (char *)malloc(strlen(s1) + 1);	// 文字数+1バイト(末尾の0のため)のメモリを確保
    strcpy(s, s1);				// 確保したメモリに文字列をコピー
    return s;					// 確保したメモリを返却
}

// 文字列を連結する
char *concat_string(char *s1, char *s2) {
    char *s = (char *)malloc(strlen(s1) + strlen(s2) + 1);
    strcpy(s, s1);				// 確保したメモリに文字列 s1 をコピー
    strcat(s, s2);				// 確保したメモリに文字列 s2 を連結コピー
    return s;					// 確保したメモリを返却
}

void main() {
    char *s1 = create_string("Hello ");	// 文字列 s1 を生成する
    char *s2 = create_string("world!");	// 文字列 s2 を生成する
    char *s3 = concat_string(s1, s2);	// s1 と s2 を連結して文字列 s3 を生成する

    printf("%s\n", s1);			// => Hello 
    printf("%s\n", s2);			// => world!
    printf("%s\n", s3);			// => Hello world!

    free(s1);				// ヒープ領域のメモリは明示的な開放が必要
    free(s2);				//  〃
    free(s3);				//  〃
}

演算子

下記の様な演算子があります。

expr1 + exprt2		// 四則演算:加算
expr1 - exprt2		// 四則演算:減算
expr1 * exprt2		// 四則演算:乗算
expr1 / exprt2		// 四則演算:除算
expr1 % exprt2		// 四則演算:剰余

expr1 & expr2		// ビット演算:論理積(AND)
expr1 | expr2		// ビット演算:論理和(OR)
expr1 ^ expr2		// ビット演算:排他的論理和(XOR)
~expr			// ビット演算:否定(NOT)
expr1 << expr2		// ビット演算:左シフト
expr1 >> expr2		// ビット演算:右シフト

expr1 == expr2		// 比較演算:expr1expr2が等しければ
expr1 != expr2		// 比較演算:expr1expr2が等しくなければ
expr1 < expr2		// 比較演算:expr1expr2より大きければ
expr1 <= expr2		// 比較演算:expr1expr2以上であれば
expr1 >= expr2		// 比較演算:expr1expr2以下であれば
expr1 > expr2		// 比較演算:expr1expr2より小さければ

expr1 && expr2		// 論理演算:expr1かつexpr2であれば(AND)
expr1 || expr2		// 論理演算:expr1もしくはexpr2であれば(OR)
!expr			// 論理演算:否定(NOT)

var = expr		// varexprを代入
var += expr		// var = var + expr と同義
var -= expr		// var = var - expr と同義
var *= expr		// var = var * expr と同義
var /= expr		// var = var / expr と同義
var %= expr		// var = var % expr と同義
var &= expr		// var = var & expr と同義
var |= expr		// var = var | expr と同義
var ^= expr		// var = var ^ expr と同義
var <<= expr		// var = var << expr と同義
var >>= expr		// var = var >> expr と同義

var++			// varの値をひとつ増やす(インクリメント)
var--			// varの値をひとつ減らす(デクリメント)

expr1 ? expr2 : expr3	// 三項演算子:expr1が真ならexpr2さもなくばexpr3

制御構文

もし~ならば(if・else)

もし n が 10 より大きければ Big! を表示します。

if (n > 10) {
    printf("Big!\n");
}

もし n が 10 より大きければ Big! を、さもなくば Not big! を表示します。

if (n > 10) {
    printf("Big!\n");
} else {
    printf("Not big!\n");
}

もし n が 10 より大きければ Big!、等しければ Equal!、さもなくば Small! を表示します。

if (n > 10) {
    printf("Big!\n");
} else if (n == 10) {
    printf("Equal!\n");
} else {
    printf("Small!\n");
}
~の間(while)

n が 10 より小さい間 { ... } の処理を繰り返します。

int n = 0;
while (n < 10) {
    printf("%d\n", n);
    n++
}
~の間(do・while)

{ ... } の処理を n が 10 より小さい間繰り返します。

int n = 0;
do {
    printf("%d\n", n);
    n++;
} while (n < 10);
~の間(for)

for (expr1; expr2; expr3) { ... } は、最初に expr1 を実行し、expr2 が真の間、expr3 および { ... } を実行します。下記は i を 0~9 に変化させながら 10回ループします。

int i;
for (i = 0; i < 10; i++) {
    printf("%d\n", i);
}

C99 では、for文の中で変数を定義できるようになりました。gcc の場合 -std=c99 オプションをつける必要があります。

for (int i = 0; i > 10; i++) { ... }
ループの中断・繰り返し(break・continue)

while, do, for のループ処理において、break はループを中断して抜けます。continue は次のループに移ります。下記の例では i が 3 の時は printf(...) を実行せずに次のループに移り、i が 5 の時はループを抜けるため、0 1 2 4 と表示されます。

for (i = 0; i < 10; i++) {
    if (i == 3) {
        continue;
    }
    if (i == 5) {
        break;
    }
    printf("%d\n", i);
}
Goto文(goto)

goto は、labelname: で定義したラベルの場所にジャンプします。

main() {
    int i;
    for (i = 0; i < 10; i++) {
        if (i == 5) {
            goto done;
        }
    }
done:
    printf("i=%d\n", i);
}
Switch文(switch・case・default・break)

switch (expr) { case ... } は、expr の値によってマッチする case ブロックを実行します。case ブロックの末尾には break を記述します。記述しない場合は次の case ブロックも実行します。default はいずれにもマッチしなかった場合に実行する処理を記述します。下記の例は、n の値が 1 であれば One を、2 であれば Two を、さもなくば More を出力します。

int n = 2;
switch (n) {
case 1:
    printf("One\n");
    break;
case 2:
    printf("Two\n");
    break;
default:
    printf("More\n");
    break;
}

プリプロセッサ

C言語では、コンパイルの前に cpp というプリプロセッサによる前処理を行います。

インクルード(#include)

#include は指定したヘッダファイル(*.h)を読み込みます。フィル名を <...> で囲んだ場合は ①コンパイル時の -I オプションで指定したディレクトリ、②C_INCLUDE_PATH 環境変数で指定したディレクトリ、③システム標準ディレクトリ(/usr/include など)の順で探しにいきます。ファイル名を "..." で囲んだ場合は、①②③の前にカレントディレクトリを探しに行きます。

#include <stdio.h>
#include "myheader.h"

デファイン(#define, #undef)

#define は定数やマクロを定義します。

#include <stdio.h>

#define MY_MESSAGE "Hello world!"	// 定数を定義
#define print(x) printf("%s\n", x)	// マクロ関数を定義

main() {
    print(MY_MESSAGE);
}

#undef は定義した定数やマクロを削除します。

#undef MY_MESSAGE
#undef print

複数の文を含むマクロを定義する際は、{ ... } を省略した if 文でも使用できるように、do { ... } while(0) で囲むことが推奨されています。

#define SWAP(a, b) do { int tmp = a; a = b; b = tmp; } while(0)

条件コンパイル(#ifdef, #else, #elif, #endif)

#ifdef#elif#else#endif は条件に合致した時のみ該当箇所をコンパイルします。

main() {
#ifdef CENTOS			// CENTOS が定義されていたら
    printf("CentOS\n");
#elif UBUNTU			// UBUNTU が定義されていたら
    printf("Ubuntu\n");
#else				// さもなくば
    printf("Unknown\n");
#endif
}

条件判断のデファインは、#define で定義したり、gcc-D オプションで定義します。

$ gcc -DCENTOS -o main main.c

関数

関数は下記の様に定義します。下記では、int 型の引数 x と y を受け取り、その合計を int 型で返却する add() 関数を定義し、x と y に 3 と 5 を当てはめて呼び出しています。

main() {
    int ans;
    ans = add(3, 5);
    printf("%d\n", ans);	// => 8
}

int add(int x, int y) {
    int z;
    z = x + y;
    return z;
}

関数の戻り値(return)

return は結果を呼び出し元に返します。

int add(int x, int y) {
    int z;
    z = x + y;
    return z;
}

可変引数

va_list, va_start(), va_arg(), va_end() を用いることで可変引数の関数を定義することができます。下記の例では、第1引数に可変引数の個数を指定し、任意個数の文字列データを出力しています。

#include <stdio.h>
#include <stdarg.h>

void print(int num, ...) {
    va_list ap;
    va_start(ap, num);
    for (int i = 0; i < num; i++) {
        printf("%s\n", va_arg(ap, char *));
    }
    va_end(ap);
}

void main() {
    print(3, "AAA", "BBB", "CCC");
}

値渡しと参照渡し

関数に引数を渡す方法として 値渡し(call by value) と 参照渡し(call by reference) があります。下記の例で、x と y は単純に値自体を引数として渡す値渡しです。ans は、ans 変数への参照(=ポインタ)(&変数名) を渡し、そのポインタの中身に答えを格納してもいらいます。int や float など小さなデータの場合は値渡し、メモリコピーが性能に影響するような大きなデータ(構造体など)の場合や、受け取る値が複数の時などは、参照渡しを利用します。

void add(int x, int y, int *ans) {
    *ans = x + y;
}

void main() {
    int x = 3;
    int y = 5;
    int z;
    add(x, y, &z);
    printf("z=%d\n", z);
}

スレッド

NPTL(Native POSIX Thread Library) 形式のスレッドは下記の様に記述します。コンパイル時に -lpthread オプションをつけて libpthread とリンクします。

#include <stdio.h>
#include <pthread.h>

void *thread_func(void *arg) {
    for (int i = 0; i < 10; i++) {
        printf("%s\n", (char *)arg);
    }
}

void main() {
    pthread_t th1;
    pthread_create(&th1, NULL, thread_func, "ThreadA");
    pthread_join(th1, NULL);
}

C11 形式のスレッドは下記の様に記述します。(gccではまだサポートされていない?)

#include <stdio.h>
#include <threads.h>

int thread_func(void *arg) {
    for (int i = 0; i < 10; i++) {
        printf("%s\n", (char *)arg);
    }
}

void main() {
    thrd_t th1;
    thrd_create(&th1, thread_func, "ThreadA");
    thrd_join(th1, NULL);
}

構造体(struct)

struct は構造体を定義します。オブジェクト指向言語の class に似ていますが、メンバ変数を定義するのみで、継承、クラスメソッド、コンストラクタ、デストラクタなどの概念はありません。

struct point {
    int x;
    int y;
}

main() {
    struct point p1;
    p1.x = 200;
    p1.y = 300;
    printf("%d, %d\n", p1.x, p1.y);
}

共用体(union)

union は共用体を定義します。共用体の各変数は同じメモリを共用するため、メモリ使用量を減らすことができますが、同時には c か n か f かいずれかひとつの変数のみを使用します。共用体のサイズは、メンバ変数の内一番大きな変数のサイズとなります。

union any_value {
    char c;
    int n;
    float f;
};

main() {
    union any_value a1, a2, a3;

    a1.c = 'A';
    a2.n = 123;
    a3.f = 12.3;
    printf("%c, %d, %f\n", a1.c, a2.n, a3.f);
}

列挙型(enum)

enum は列挙型を定義します。列挙型のメンバには 0, 1, 2, ... の数値が順に割り振られます。一連の名前定義に連番を自動的につけたい時に有効です。

enum color {
    color_red,
    color_green,
    color_blue
};

main() {
    enum color c = color_blue;

    printf("%d\n", c);			// => 2
}

サイズ参照(sizeof)

sizeof() は型、値、変数、構造体、共用体などのサイズ(バイト数)を返します。

#include <stdio.h>

struct point { int x; int y; };

main() {
    int n = 123;
    struct point p;
    printf("%d\n", sizeof(int));	// => 4
    printf("%d\n", sizeof(123));	// => 4
    printf("%d\n", sizeof(n));		// => 4
    printf("%d\n", sizeof(p));		// => 8
}

外部参照(extern)

extern を宣言することで、別のファイルで定義されたグローバル変数や関数を、外部変数、外部関数として参照することが可能となります。下記の例では sub.c で定義されたグローバル変数 g_value や関数 g_hello() を、main.c の中で外部変数、外部関数として参照しています。

sub.c
#include <stdio.h>

int g_value = 123;		// グローバル変数を定義

g_hello(char *msg) {		// 関数を定義
    printf("Hello %s!\n", msg);
}
main.c
#include <stdio.h>

extern int g_value;		// 外部変数として参照
extern void g_hello(char *);	// 外部関数として参照

main() {
    printf("%d\n", g_value);	// => 123
    g_hello("Yamada");		// => Hello Yamada!
}

通常は、sub.c が提供する変数や関数をヘッダファイルとして用意しておき、それを利用します。

sub.h
extern int g_value;		// 外部変数として参照
extern void g_hello(char *);	// 外部関数として参照
sub.h
#include <stdio.h>
#include <sub.h>

main() {
    printf("%d\n", g_value);	// => 123
    g_hello("Yamada");		// => Hello Yamada!
}

typeof演算子(typeof)

typeofgcc による拡張機能で、型・変数・値の型を返します。

typeof(int) a;	// int a; と同義
typeof(a) b;	// int b; と同義
typeof(123) c;	// int c; と同義

複数の型に対応したマクロを定義する際に便利です。

#define SWAP(a, b) do { typeof(a) tmp = a; a = b; b = tmp; } while (0)

揮発変数(volatile)

volatile はその変数が揮発性であることをコンパイラに伝えます。揮発性があるとは、その変数が非同期に変更される可能性があることを意味します。下記の例では別スレッドが stop_flag を 0 以外に変更することで無限ループを抜けますが、volatile を宣言しない場合、コンパイラはループの中で stop_flag が変化することは無いと推測し、stop_flag 判定を省略するような最適化を行ってしまう可能性があります。

volatile int stop_flag = 0;

func() {
    while (!stop_flag) {
        printf(".");
    }
}

特殊変数

特殊変数 __FILE__ はプログラムファイル名を、__LINE__ はその行数を、__func__ は関数名を示します。__FILE____LINE__ はプリプロセッサが、__func__ はコンパイラが処理します。

main() {
    printf("%s(%d): %s\n", __FILE__, __LINE__, __func__);
}

C99で追加された機能

1999年の C99 では下記などの機能が追加されました。

真偽値(_Bool)

C99 でブーリアン型 _Bool が追加されました。真(true) または 偽(false) どちらかの値をとります。既存プログラムに影響を与えないように _大文字で始まる名前になっています。

#include <stdbool.h>

main() {
    _Bool flag = true;

    printf("flag = %d\n", flag);
}

複素数(_Complex)

C99 で複素数型 _Complex が追加されました。creal() は実数部を、cimag() は虚数部を参照します。虚数部を示す型として _Imaginary も定義されましたが、gcc ではまだサポートされていない?

#include <stdio.h>
#include <complex.h>

main() {
    double _Complex a = 1.2 + 3.4i;

    printf("%f%+fi\n", creal(a), cimag(a));
}

インライン関数(inline)

inline は関数をインライン関数として定義したい場合に使用します。C99 で追加されました。インライン関数はコンパイル時に呼び出し元関数の内部処理として展開されます。何度も呼び出す小さな関数を内部処理にして高速化することが目的です。実際にインライン展開されるか否かは、コンパイラの最適化によって左右されます。

inline int add(int x, int y) {
    return x + y;
}

main() {
    printf("%d\n", add(3, 5));
}

制限付きポインタ(restrict)

restrict は複数のポインタが重複する領域を含んでいないことをコンパイラに伝えます。C99 で導入されました。restrict を宣言しても動作上の差異はありませんが、コンパイラは、p1 と p2 が同じ重複する領域を含んでいないことを確信して、より高速な最適化アルゴリズムを選択することができるようになります。

func(int n, int * restrict p1, int * restrict p2) {
	...
}

C11で追加された機能

2011年の C11 では下記などの機能が追加されました。

アラインオブ(_Alignof)

_Alignof(type)type で指定した型のアライン値を求めます。アライン値とは、その型の変数や値がメモリ上に配置される際に、何バイトの整数倍のアドレスに配置される必要があるかという値です。例えばアライン値が 4 の場合、その型の変数や値は 4の倍数のメモリに配置される必要があります。アライン値は CPU やシステムによって異なります。C11 で追加されたもので、既存プログラムに影響を与えないよう _大文字 で始まる名前になっています。gcc などでは <stdalign.h>_Alignof の別名 alignof が定義されています。

main() {
    printf("%d\n", _Alignof(short));	// => 2 (2の倍数のアドレスに配置される必要がある)
    printf("%d\n", _Alignof(long));	// => 8 (8の倍数のアドレスに配置される必要がある)
}

アラインアズ(_Alignas)

_Alignas(n) は、宣言する変数に対して n バイトのアライン値を要求します。下記の例では b は通常 a の次のアドレスに配置されますが、_Alignas によって 4の倍数のアドレスに配置されます。C11 で追加されたもので、既存プログラムに影響を与えないよう _大文字 で始まる名前になっています。gcc などでは <stdalign.h>_Alignas の別名 alignas が定義されています。

struct data {
    char a;
    _Alignas(4) char b;
};

アトミック(_Atomic)

_Atomic は宣言する変数に対してアトミック性の保証を要求します。マルチスレッドプログラミングにおけるアトミック性とは、複数のスレッドが同時に参照・変更することによる不整合が生じないことを意味します。アトミック性が保証されない場合、total_count++ という処理は、①メモリからレジスタに読み込む、②レジスタの値をインクリメントする(ひとつ増やす)、③レジスタの値をメモリに書き戻すという動作となり、複数のスレッドが同時に countup() 関数を呼んだ場合、countup() が呼ばれた回数より total_count の値が小さくなることがあります。_Atomic はこの問題を防ぎます。初期化には <stdatomic.h> で定義される ATOMIC_VAR_INIT() を用いるなどいくつかの制約があります。C11 で追加されました。

#include <stdatomic.h>

_Atomic int total_count = ATOMIC_VAR_INIT(0);

void countup() {
    total_count++;
}

ジェネリック(_Generic)

_GenericC11 で追加されたもので、複数の型に対応したマクロを定義することができます。下記の例では、引数の型が int の時には print_int() を、double の時には print_double() を、char * の時には print_string() を呼び出します。gcc 4.9 からサポートされています。

#include <stdio.h>

void print_int(int x) { printf("%d\n", x); }
void print_double(double x) { printf("%f\n", x); }
void print_string(char *x) { printf("%s\n", x); }

#define print(x) _Generic((x), \
    int:    print_int, \
    double: print_double, \
    char *: print_string \
)(x)

void main() {
    print(123);		// => 123
    print(1.23);	// => 1.230000
    print("ABC");	// => ABC
}

ノーリターン(_Noreturn)

_Noreturn 指示子は C11 で追加されたもので、関数が戻らないことをコンパイラに伝えます。これによりコンパイラは、警告を抑制したりより効率の良い最適化を行うことがあります。

_Noreturn void myexit() {
    exit(0);
}

main() {
    myexit();
}

静的アサート(_Static_assert)

_Static_assertC11 で追加されたもので、コンパイル時のチェック(アサーション)を行います。第1引数には論理式、第2引数にはエラーメッセージを記述します。論理式にはコンパイル時に判断できるものである必要があります。

int f(int x) {
    _Static_assert(sizeof(x) == 8, "Size of x is not 8");
    return x;
}

main() {
    f(123);
}

スレッドローカル(_Thread_local)

_Thread_localC11 で追加されたもので、指定した変数の存在期間をスレッドの存在期間に限定します。gcc では __thread として実装しているようです。

#include <stdio.h>
#include <pthread.h>

void *thread_func(void *param) {
    static int a1 = 0;
    static _Thread_local int a2 = 0;
    for (int i = 0; i < 10; i++) {
        printf("%d %d\n", a1++, a2++);
    }
}

void main() {
    pthread_t th1, th2;
    pthread_create(&th1, NULL, thread_func, "ThreadA");
    pthread_create(&th2, NULL, thread_func, "ThreadB");
    pthread_join(th1, NULL);
    pthread_join(th2, NULL);
}

Copyright (C) 2020 杜甫々
初版:2020年7月26日 最終更新:2020年7月26日
http://www.tohoho-web.com/ex/c-lang.html