動的なメモリ割り当て

動的なメモリ割り当て(dynamic allocation)とは、プログラムの実行中に必要に応じてメモリを割り当てる処理のこと。これまで使ってきた変数は、必要なメモリがコンパイル時に固定されているのに対して、動的なメモリ割り当ては実行時にメモリを割り当てることができる。

メモリを割り当てるには、malloc() 関数を使う。

void *malloc(size_t numbytes);

引数の「numbytes」は割り当てたいメモリのバイト数で、戻り値は割り当てられたメモリブロックの先頭を指すポインタ。メモリ割り当てに失敗した時にはヌルポインタを返す。

動的に割り当てたメモリが必要なくなったら、メモリを開放しなければいけない。これには free() 関数を使う。

void free(void *ptr);

もっとも、プログラムが終了すると、割り当てられていたメモリは全て自動的に解放される。
例えば入出力用のバッファなんかは一時的にメモリが必要になるので、malloc() でメモリを割り当て、要らなくなったら free() で解放する。

次のプログラムは、ユーザの入力(文字列)を受け取るメモリブロックを動的に割り当て、必要がなくなったら解放している。

#include
#include

int main(void)
{
    char *p;

    p = (char *)malloc(80);

    if (!p) {
        printf("Fail to allocate.\n");
        exit(1);
    }

    printf("Input string: ");
    gets(p);
    printf("%s\n", p);

    free(p);

    return 0;
}
takatoh@nightschool $ ./sample_12_7
Input string: Hello!
Hello!

関数ポインタ

関数ポインタは、関数のエントリポイントのアドレスを保持する変数。
コンパイラは、プログラムをコンパイルするときに関数のエントリポイントを作成する。エントリポイントはアドレスなので、それを指すポインタ変数を持つことが可能だ。それが関数ポインタ。

関数ポインタを宣言するには、その関数の戻り値の型のポインタとして、関数に引数があればそれも続けて宣言する。

int (*p) (int x, int y);

優先順位の規則のため、*p はカッコで囲む必要がある。

関数のアドレスを関数ポインタに代入するには、関数名をカッコを付けずに指定する。例えば、次のような関数 sum() があるとすると:

int sum(int a, int b);

次のように代入する。

p = sum;

代入した関数を呼び出すには:

result = (*p) (10, 20);

呼び出し時にも *p をカッコで囲む必要がある。

戻り値の型と引数の数、型が同じであれば、関数ポインタを配列にすることもできる。

int (*p)[4] (int x, int y);

次のプログラムは、ユーザに2つの整数を入力させ、次に行いたい演算の番号を入力させる。そしてその結果を表示する。
ユーザの入力した番号から演算を選ぶ部分で関数ポインタの配列を使っている。

#include

int sum(int a, int b);
int subtract(int a, int b);
int mul(int a, int b);
int dev(int a, int b);

int (*p[4]) (int x, int y);

int main(void)
{
    int result;
    int i, j, op;

    p[0] = sum; /* Get address of sum() */
    p[1] = subtract; /* Get address of subtract() */
    p[2] = mul; /* Get address of mul() */
    p[3] = dev; /* Get address of dev() */

    printf("Input 2 numbers:\n");
    scanf("%d%d", &i, &j);
    printf("0: sum, 1: subtract, 2: mul, 3: dev\n");
    do {
        printf("Input number of operation:\n");
        scanf("%d", &op);
    } while ((op < 0) || (3 < op));
    result = (*p[op]) (i, j);
    printf("%d\n", result);
    return 0;
}

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

int subtract(int a, int b)
{
    return a - b;
}

int mul(int a, int b)
{
    return a * b;
}

int dev(int a, int b)
{
    if (b) {
         return a / b;
    } else {
        return 0;
    }
}
takatoh@nightschool $ ./sample_12_6
Input 2 numbers:
57
24
0: sum, 1: subtract, 2: mul, 3: dev
Input number of operation:
2
1368

#演算子と##演算子

C のプリプロセッサには2つの演算子がある。# と ## だ。

#演算子

#演算子はマクロ関数の引数を、引用符付きの文字列に変換する。

#include

#define MKSTRING(str) # str

int main(void)
{
    int value;

    value = 10;

    printf("%s: %d\n", MKSTRING(value), value);

    return 0;
}
takatoh@nightschool $ ./sample_12_5a
value: 10

13行目の MKSTRING(value) が “value” に置き換えられている。

##演算子

##演算子は2つの識別子を連結する。

#include

#define output(i) printf("%d %d\n", i ## 1, i ## 2)

int main(void)
{
    int count1, count2;

    count1 = 10;
    count2 = 20;

    output(count);

    return 0;
}
takatoh@nightschool $ ./sample_12_5b
10 20

outputマクロの引数 count が、##演算子によって 1 と 2 が連結され、count1 と count2 になっている。

組み込みマクロ

ANSI C 標準に準拠したコンパイラなら、次の5つの定義済みマクロがある。

__FILE__現在コンパイル中のファイル名
__LINE__現在コンパイル中の行番号(整数値)
__DATE__現在の日付(文字列)
__TIME__コンパイルを開始した時刻(文字列)
__STDC__コンパイラが ANSI C 標準に準拠しれいれば 1

次のプログラムは、__FILE__, __LINE__, __DATE__, __TIME__ を使った例。

#include

int main(void)
{
    printf("Compiling: file: %s, line: %d at %s %s\n",
    __FILE__, __LINE__, __DATE__, __TIME__);

    return 0;
}
takatoh@nightschool $ ./sample_12_4a
Compiling: file: sample_12_4a.c, line: 7 at Jun 13 2015 15:57:24

__DATE__, __TIME__ はコンパイルしたときに値が決まるので、プログラムをいつ実行しても同じ日付、時刻を出力する。

__STDC__ についても試してみよう。

#include

int main(void)
{
    printf("%d\n", __STDC__);

    return 0;
}

結果は:

takatoh@nightschool $ ./sample_12_4b
1

#error、#undef、#line、#pragma

#error

#error エラーメッセージ

プリプロセッサは #error ディレクティブを見つけるとコンパイルを中断する。

#include

int main(void)
{
    int i;

    i = 10;

    #error This is an error message.

    printf("%d\n", i); /* This line is NOT compiled */

    return 0;
}
takatoh@nightschool $ gcc sample_12_3a.c -o sample_12_3a
sample_12_3a.c: In function ‘main’:
sample_12_3a.c:10:2: error: #error This is an error message.
 #error This is an error message.
  ^

#error ディレクティブは主にデバッグ目的で使われる。

#undef

#undef マクロ名

#undef ディレクティブはマクロ名の定義を解除する。マクロ名が定義されていなければなんの効力もない。#undef は主にマクロ名の効果を局所的に限定するためにある。

#include

#define DOG

int main(void)
{
    #ifdef DOG
    printf("DOG is defined.\n");
    #endif

    #undef DOG

    #ifdef DOG
    printf("This line is NOT compiled.\n");
    #endif

    return 0;
}
takatoh@nightschool $ ./sample_12_3b
DOG is defined.

#line

C のコンパイラは、コンパイルの最中、現在コンパイルしているファイル名と行番号を保持している。#line ディレクティブを使うと、これらの値を変更することができる。一般的な形式は次のとおり。

#line 行番号 "ファイル名"

主にデバッグや大規模プロジェクトの管理に使用する、と本には書いてあるけど、ユースケースが思い浮かばない。

#include

int main(void)
{
    int i;

    #line 1000 "myprog.c"
    #error Check the line number and file name.

    return 0;
}
takatoh@nightschool $ gcc sample_12_3c.c -o sample_12_3c
myprog.c: In function ‘main’:
myprog.c:1000:2: error: #error Check the line number and file name.

確かに行番号やファイル名が #line ディレクティブで指定した値になっている。

#pragma

#pragma 命令

本には「#pragma ディレクティブを使うと、コンパイラに渡すプリプロセッサディレクティブを、コンパイラの実装者が定義することができます。」と書いてある。これって、コンパイラを使うプログラマには使い道がないんじゃ……。

条件付きコンパイル

C のプリプロセッサには、ソースコードの一部を選択的にコンパイルできるようにするディレクティブがある。そういうディレクティブを使ったコンパイル方法を条件付きコンパイル(conditional compilation)という。条件付きコンパイルのためのディレクティブは次のとおり。

  • #if
  • #else
  • #elif
  • #endif
  • #ifdef
  • #ifndef

#if から #endif までは、要するに if 文と一緒だ。一般的な形式は次のとおり。

#if 定数式1
    文の並び
#elif 定数式2
    文の並び
#elif 定数式3
    文の並び
#else
    文の並び
#endif

定数式は真の時だけ、文の並びかコンパイルされる。ここで注意が必要なのは、定数式の部分に変数は使えないこと。プリプロセッサがこれを処理するときにはまだ変数が使えないからだ。

条件付きコンパイルのもうひとつの方法として、#ifdef あるいは #ifndef をつかつ方法がある。

#ifdef マクロ名
    文の並び
#endif

#ifdef はマクロ名が定義されているときだけ、文の並びをコンパイルする。#ifndef はその逆に定義されていないときにコンパイルする。

次のプログラムは、#if #else #endif を使った例。マクロ CHAR_SET の値によって、コンパイルする文が変わる。

#include

#define CHAR_SET 128

int main(void)
{
    int i;

    #if CHAR_SET == 256
    printf("ASCII and other characters.\n");
    #else
    printf("Only ASCII characters.\n");
    #endif

    for (i = 0; i < CHAR_SET; i++) {
        printf("%c", i);
    }
    printf("\n");

     return 0;
}
takatoh@nightschool $ ./sample_12_2
Only ASCII characters.



 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

#define CHAR_SET の値を 256 に変えると出力は接ぎのように変わる。

takatoh@nightschool $ ./sample_12_2
ASCII and other characters.



 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~������������������������������������������������������������������������������������������������������������������������������

なんか表示できない文字が多いけど、出力が、ということはコンパイル内容が変わっていることはわかる。

#includeディレクティブ

#include ディレクティブの書き方には2通りの書きかたがある。

#include <ヘッダファイル>
#include "ヘッダファイル"

それぞれ、ヘッダファイルを探す場所が異なる。< > で囲んだ場合は、処理系の標準ヘッダファイルが置いてあるディレクトリから探す。一方、” ” で囲んだ場合は、別の方法、たいていはカレントディレクトリから探し、見つからない場合は標準のディレクトリから探す。

通常、自分で作ったヘッダファイルを #include するときに、” ” を使う。

マクロ関数

#define プリプロセッサディレクティブには、単純な文字列を置き換えのほかに、関数形式のマクロ(function-like macro)、すなわちマクロ関数という使い方もある。
マクロ関数を使うと、プリプロセッサによって置き換えられる文字列に引数を渡すことができる。

#incluce

#define SUM(i, j) i + j

int main(void)
{
    int sum;

    sum = SUM(10, 20);
    printf("%d\n", sum);

    return 0;
}

11行目の

sum = SUM(10, 20);

はプリプロセッサによって

sum = 10 + 20;

に置き換えられる。この例は単純だけど、プログラム中に同じ処理が何度も出てくるときには、マクロ関数が使える。もちろん普通の関数にしてもいいけど、関数呼び出しのオーバーヘッドがないぶん有利。ただ、マクロ関数が出てくる場所全部に同様のコードが複製されるので、プログラムが大きくなる。

……と、こう書くとマクロ関数にしたほうがいいのか、普通の関数のほうがいいのかよくわかんないなあ。

演算子の優先順位

演算子の優先順位のまとめ。

優先順位 演算子
高い ( ) [ ] -> .
! ~ + – ++ — (型キャスト) * & sizeof
* / %
+ –
<< >>
< <= > >=
== !=
&
^
|
&&
||
?:
= += -+ *= /* など
低い ,

なんか2度出てくる演算子があるけど?

[追記]

2度出てくる演算子についてコメントをもらった。片方(たぶん優先順位の高いほう)は単項演算しだそう。なるほどね。
コメントありがとうございます。

カンマ演算子

カンマ(,)って演算子だったんだ!
カンマ演算子は、式を続けて書くときに使う。よく使うのは for ループ。

for (i = 0, j = 0; (i + j) < count; i++, j;;)
    ...

ループ変数に i と j を使って、それぞれ初期化、インクリメントをしている。

カンマで区切られた式のリストの値は、最も右の式の値になる。次の文では、value には 100 が代入される。

value = (count, 99, 33, 100);

式のリストにカッコをつけているのは、代入演算子よりもカンマ演算子のほうが優先順位が低いから。