条件付きコンパイル

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

シフト演算子

シフト演算子は、変数の各ビットを左または右に指定したビット数だけ移動する。移動して範囲からはみだしたビットは消失し、その反対側のビットは 0 で埋められる。

<<左シフト
>>右シフト

シフト演算子の一般的な形式は次のとおり。

値 << ビット数;
値 >> ビット数;

次のプログラムは、左に 1 ビットシフトしたあと、右に 1 ビットシフトしている。元の値には戻らずに、いちばん左のビット(1)が失われているのがわかる。

#include

void show_binary(unsigned short u);

int main(void)
{
    unsigned short u;

    u = 45678;

    show_binary(u);
    u = u << 1;
    show_binary(u);
    u = u >> 1;
    show_binary(u);

    return 0;
}

void show_binary(unsigned short u)
{
    unsigned short n;

    for (n = 32768; n > 0; n = n / 2) {
        if (u & n) {
            printf("1");
        } else {
            printf("0");
        }
    }

    printf("\n");
}
takatoh@nightschool $ ./sample_11_6
1011001001101110
0110010011011100
0011001001101110

ビットごとの演算子

ビット単位で演算を行う演算子として次の4つがある。

&ビットごとの AND
|ビットごとの OR
^ビットごとの XOR(排他的論理和)
~1 の補数

1 の補数というのは、ビットの 0 と 1 を反転させる単項演算子だ。次のプログラムで、その動作を確認することができる。

#include

void print_binary(char ch);

int main(void)
{
    char ch;

    ch = 'a';

    print_binary(ch);

    ch = ~ch;

    print_binary(ch);

    return 0;
}

void print_binary(char ch)
{
    int i;

    for (i = 128; i > 0; i = i / 2) {
        if (i & ch) {
            printf("1");
        } else {
            printf("0");
        }
    }
    printf("\n");
}
takatoh@nightschool $ ./sample_11_5
01100001
10011110

ちゃんと各ビットが反転してるね。

typedef

typedef は既存の型に新しい名前をつける。一般的な形式は次のとおり。

typedef 既存の型名 新しい型名;

次の例では、signed char に新しい型名 smallint をつけて、変数 i を宣言するのに使っている。

#include

typedef signed char smallint;

int main(void)
{
    smallint i;

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

    return 0;
}

typedef を使っても元の型名が無効になるわけではない。上の例では signed char も型として使える。 また、typedef を何度も使って同じ型に違う名前をいくつもつけることができる。しかも、typedef でつけた型名は、既存の型として扱うこともできる。次の例では、変数 d の型は depth として宣言しているけど、実体としては int だ。

typedef int height;
typedef height width;
typedef width depth;
depth d;

typedef を使って新しい型名をつけるのには、2つのメリットがある。 1つはプログラムの移植性に関わる問題で、例えば2バイト長の変数が使いたい場合、int が2バイトの環境では次のようにして、変数の型を myint にしておく。

typedef int myint;
myint i;

そして、2バイトの型が short int の環境に移植するときには、typedef 文だけを変更すればいい。

typdef short int myint;

プログラム中で使っている myint を修正する必要はない。 もう1つは、型名に自己説明的な名前をつけることができることだ。次のようにしておけば、subtotal として宣言された変数が「小計」を表すことをプログラムから読み取れる。

typedef double subtotal;