#演算子と##演算子

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);

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

代入演算子の応用

多重代入

変数1 = 変数2 = 変数3 = ... = 変数N = 値;
#include

int main(void)
{
    int i, j, k;

    i = j = k = 99;

    printf("%d %d %d\n", i, j, k);

    return 0;
}
takatoh@nightschool $ ./sample_11_8a
99 99 99

演算子の省略記法

例えば

x = x + 3;

という代入があったとき、これを省略記法で

x += 3;

と書ける。省略記法で書ける演算子は次のとおり。

+ - * / % << >> & | ^
#include

int main(void)
{
    int i;

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

    return 0;
}
takatoh@nightschool $ ./sample_11_8b
0
2
4
6
8
10
12
14
16
18

条件演算子(?)

Ruby にもある三項演算子 ? : 。

変数 = 条件 ? 式1: 式2;

次のプログラムは、入力された整数が 0 以上であれば 1 を、0 より小さければ -1 を表示する。

#include

int main(void)
{
    int i;

    printf("Input number: ");
    scanf("%d", &i);

    i = i >= 0 ? 1: -1;

    printf("%d\n", i);

    return 0;
}
takatoh@nightschool $ ./sample_11_7
Input number: 7
1
takatoh@nightschool $ ./sample_11_7
Input number: -3
-1