目次
識別子
変数や関数、記号定数の名前のことを識別子と呼びます。識別子は自由に命名できますが、以下の制約があります。
- 半角英数字(
a〜z,A〜Z,0〜9)および半角アンダーバー(_)のみ使用できる。 - 最初の1文字目は半角英字(
a〜z,A〜Z)または半角アンダーバー(_)のみ使用できる。 - 大文字と小文字は区別される。つまり、
wordとWORDは別の識別子として認識される。 - 予約語は使用できない。ただし、識別子の一部に予約語を含めることはできる。
予約語
予約語とはC言語において固有の意味を持つ単語です。そのため、予約語は識別子として用いることができません。C言語の予約語を以下に示します。
- auto
- break
- case
- char
- const
- continue
- default
- do
- double
- else
- extern
- float
- for
- goto
- if
- int
- long
- register
- return
- short
- signed
- sizeof
- static
- struct
- switch
- typedef
- union
- unsigned
- void
- volatile
- while
変数
変数とは、プログラムで扱われる数値や文字列などの値を一時的に記憶しておく領域のことです。変数を識別するために、ひとつひとつの変数に固有の名前を付けておきます。これを変数名といいます。
変数を使用する前に、どのような変数を使用するか、その変数名や変数に記憶できるデータ型を明らかにしておくことを変数の宣言といいます。
C 言語で変数を宣言する書式を次に示します。
記憶クラスおよび初期値は省略することができます。
記憶クラスおよびデータ型が同じ変数は、カンマで区切って複数宣言することができます。
変数の記憶クラス
記憶クラスとは、宣言する変数をどこに記憶するかを指定します。
- 自動変数
- 静的変数
- レジスタ変数
自動変数
自動変数を表す修飾子は auto です。自動変数を宣言するC言語のソースコードを次に示します。
自動変数を宣言するには、記憶クラスに auto を指定します。自動変数を宣言する例を次に示します。
記憶クラスを表す修飾子を省略して変数を宣言した場合も自動変数となります。
自動変数を宣言する際、明示的に初期化を行わなければ、初期値は不定値になります。
静的変数
静的変数は、プログラム実行中に常に同じ場所に配置され、値を保持します。
静的変数を宣言するには、記憶クラスに static を指定します。静的変数を宣言する例を次に示します。
静的変数を宣言する際、明示的に初期化を行わなければ、初期値は0になります。
| 記憶クラス | 初期値 |
|---|---|
| 自動変数 | 不定 |
| 静的変数 | 0 |
レジスタ変数
レジスタ変数は、変数の領域を主記憶装置ではなく、極力CPUのレジスタに確保する変数です。変数の領域がレジスタに確保されると、高速にアクセスすることができます。ただし、必ずしもレジスタに確保される保障はありません。
レジスタ変数を宣言するには、記憶クラスに register を指定します。レジスタ変数を宣言する例を次に示します。
データ型
基本データ型
C 言語の基本データ型には次のような種類があります。
- char
- 8ビット符号付き整数型
- short
- 16ビット符号付き整数型
- int
- 16ビットまたは32ビット符号付き整数型
- long
- 32ビットまたは64ビット符号付き整数型
- long long
- 64ビット符号付き整数型 (Solaris)
- enum
- 列挙型
- float
- 単精度浮動小数点数型 (32ビット)
float 型では単精度浮動小数点演算ができます。
float f1, f2, f3;
f1 = f2 + f3;旧仕様 (K&R) では次のような順番で演算されました。
- 両オペランド(f2とf3)を倍精度化
- 倍精度(double型)で演算
- 倍精度の結果を単精度化
- 単精度の結果を f1 に格納
しかし、ANSI ではすべて単精度で演算されます。
- double
- 倍精度浮動小数点数型 (64ビット)
- long double
- 128ビット浮動小数点数型 (Solaris)
- void
-
voidは引数または戻り値が無いことを表す特殊なデータ型です。たとえば、引数を取らない関数は次のように定義します。int getchar(void);戻り値を返さない関数は次のように定義します。
void exit(int status);void *は汎用の(型に依存しない)ポインタ型を表します。たとえば、汎用のポインタ型を引数に取る関数は次のように定義します。void free(void *ptr);汎用のポインタ型を返す関数は次のように定義します。
void *malloc(size_t size);汎用型ポインタは型を特定することができないため、ポインタが指し示す値を直接参照することができません。他のポインタ型に変換してから参照します。
void *p; int *i; p = malloc(sizeof(int)); *p = 0; /* NG */ i = p; *i = 0; /* OK */
void **のようにvoid型のポインタへのポインタにすることはできません。
派生データ型
基本データ型の他にシステムコールやライブラリ関数で使用される派生データ型があります。 実際は何らかの基本データ型なのですが、移植性を高めるために別の名前が付けられています。
派生データ型はマクロで定義されているため、ヘッダーファイルをインクルードする必要があります。派生データ型によってインクルードするヘッダーファイルの名前は異なりますが、たいていの派生データ型は sys/types.h をインクルードすることで使用できるようになります。
派生データ型には、次のようなものがあります。
- clock_t
- システムの時間をクロック刻み (clock tick) で表す型
- dev_t
- デバイス番号に使用される型
- off_t
- ファイルサイズとオフセット(先頭からの相対位置)用に使用される型
- ptrdiff_t
- 2つのポインタの減算結果を示す符号付き整数型
- size_t
- メモリー内のオブジェクトのサイズ(バイト単位)用に使用される型
- ssize_t
- バイト数またはエラーのどちらを返すこともある関数によって使用される符号付きサイズ型
- time_t
- 秒単位の時間用に使用される型
- intptr_t
- ポインタを格納するために十分なサイズの符号付き整数型 (Solaris)
- uintptr_t
- ポインタを格納するために十分なサイズの符号なし整数型 (Solaris)
union (共用体)
union (共用体)は、同一のデータ領域を複数の異なるデータ型が共用するようにしたものです。
union タグ名 {
変数宣言のリスト
};
以下の場合には、タグ名を省略することが可能です。
- 共用体の宣言と同時に共用体変数を宣言する場合
- typedefで共用体に別名を付ける場合
変数宣言のリストの中に共用体を入れて、入れ子構造にすることも可能です。
共用体内の最初のメンバの初期化が可能です。
union x {
char a[4];
int b;
};
union x ux = { 'U', 'N', 'I', 'X' };
typedef
typdefは、既存の型に対して新しい名前を付けるときに使用します。
新しい型名を付けると、既存の型名のように使用することができます。
BYTE data;
この例は、次のように変数を宣言するのと同じです。
型名の代りに、構造体や共用体の宣言を指定することもできます。
typedef struct {
変数宣言のリスト
} 新しい型名;
typedef union {
変数宣言のリスト
} 新しい型名;
演算子
sizeof
sizeof演算子は、データ型や変数の大きさをバイト単位で返します。
例えば sizeof(short)は2を返します。
配列を指定したときは、配列全体の大きさを返します。例えば short s[32]; という配列があった場合、sizeof(s) は64を返します。
インクリメント演算子
インクリメント演算子は、変数の値に1を加える演算子です。変数の前または後に ++ を付けます。
インクリメント演算子を変数の前に付けるか、後に付けるかで若干動作が異なります。どちらも変数の値に1を加えることは共通ですが、変数の値の評価の仕方が違います。
インクリメント演算子を変数の前に付けた場合、変数の値に1を加えてから、変数の値を評価します。インクリメント演算子を変数の後に付けた場合、変数の値を評価してから、変数の値に1を加えます。
たとえば、次のようなC言語のソースプログラムがあったとします。
a = ++b;
この場合、まず変数 b の値である1に1を加えるので、変数 b の値が2になります。その後、変数 b の値である2が変数 a に代入されます。
また、次のようなC言語のソースプログラムがあったとします。
a = b++;
この場合、まず変数 b の値である1が変数 a に代入されます。代入が終わった後に、変数 b の値である1に1を加えて変数 b の値が2になります。
インクリメント演算子を使用したC言語のソースプログラムの例を次に示します。
#include <stdio.h>
int main()
{
int a = 1, b = 1, c, d;
c = a++;
d = ++b;
printf("a=%d, b=%d, c=%d, d=%d\n", a, b, c, d);
}
上記のプログラムの実行結果は次のようになります。
演算子の優先順位
演算子には優先順位があります。次に演算子の優先順位を示します。
| 優先順位 | 演算子の種類 | 演算子 | |
|---|---|---|---|
| 高い ↑ ↓ 低い |
左から右 | 関数、添え字、構造体、 後置インクリメント、後置デクリメント |
() [] . -> ++ -- |
| 右から左 | 前置インクリメント、前置デクリメント、単項式 | ++ -- ! ~ + - * & sizeof |
|
| キャスト | (型) |
||
| 左から右 | 剰除余 | * / % | |
| 加減 | + - | ||
| シフト | << >> | ||
| 比較 | < <= > >= | ||
| 等値、非等値 | == != | ||
| ビット AND | & | ||
| ビット XOR | ^ | ||
| ビット OR | | | ||
| 論理積 | && | ||
| 論理和 | || | ||
| 右から左 | 3項演算子 | ? : | |
| 代入演算子、複合代入演算子 | = += -= *= /= %= &= ^= |= <<= >>= |
||
| 左から右 | 順次演算子 | , | |
(優先順位の高い演算子である)括弧で括ることにより、演算子の優先順位を変えることができます。
次の例では、まず b * c が計算されて、その値と a が足し算されます。
括弧で括ることにより、a + b を先に計算させることができます。
修飾子
修飾子はデータ型を修飾します。
const
const は定数を表す修飾子です。例として strcpy 関数のプロトタイプ宣言を見てみましょう。
strcpy 関数は文字列 string2 を string1 へコピーする関数です。 string2 はコピー元の領域であり、コピー後も内容は変化しません。 strcpy 関数にとっては入力パラメーターであり、定数であるため const 修飾子が付いています。 一方、string1 はコピー先の領域であり、コピー後は内容が変化します。 strcpy 関数にとっては出力パラメーターであり、変数であるため const 修飾子が付いていません。
volatile
volatile修飾子は最適化を抑制するオブジェクトを宣言します。
while (1) if (comm == 0) break;
もし volatile 修飾子が無ければ、Cコンパイラーにより最適化が行われます。 ソースプログラムではループの中で変数が0に等しいかどうか比較していますが、変数が0になることは無いのでCコンパイラーの最適化機能で無駄な比較が削除されることがあります。 このため無条件で無限ループとなります。 volatile 修飾子があればコンパイラーによる最適化が行われないため、(他のプロセスから値を変更するなどして)変数 comm の値が0になるとループを脱出します。 これは、タスク間の同期制御に利用できます。
ライブラリ関数
文字列
char *strtok(char *s1, const char *s2 );
引数s1で指定した文字列の中に引数s2で指定した区切り文字(文字列形式で複数種類の文字を指定可能)が存在すれば、全てNUL文字('\0')に置き換えます。
つまり、1つの文字列を区切り文字によって複数の文字列に分割します。この分割されたそれぞれの文字列をトークンといいます。
例えば、以下のような文字列 s1 があったとします。
'A' | 'B' | ',' | 'C' | 'D' | ',' | 'E' | 'F' | '\0' |
strtok(s1, ",") を実行すると、文字列 s1 は次のようになります。
'A' | 'B' | '\0' | 'C' | 'D' | '\0' | 'E' | 'F' | '\0' |
また、strtokは最初のトークン ("AB") の先頭の文字 ('A') のポインタを返します。
次に、strtok(NULL, ",") を実行すると、strtokは次のトークン ("CD") の先頭文字 ('C') のポインタを返します。
次にstrtok(NULL, ",") を実行すると、strtokは次のトークン ("EF") の先頭文字 ('E') のポインタを返します。
最後のトークンの先頭文字のポインタを返した後にstrtok(NULL, ",") を実行すると、strtokは戻り値として NULL を返します。
時刻
現在の時刻を取得するには、time() 関数を使用します。
#include <time.h>
time_t time(time_t *tloc);
time_t は sys/types.h の中で typedef により unsigned int または unsigned long として定義されています。
引数 tlocには time_t 型の変数のポインタを指定します。time() 関数により、tloc で指定した time_t 型の変数の中に1970年1月1日0時0分0秒からの経過秒数が格納されます。
戻り値には 1970年1月1日0時0分0秒からの経過秒数が返されます。
tloc に格納される値と time() 関数の戻り値は同じです。引数に NULL を指定することもできます。
t = time(&t);
t = time(NULL); /* 引数にNULL指定 */
time(&t); /* 戻り値は無視 */
時刻を表すデータ型には、time_t と struct tm があります。これらを相互に変換したり、文字列に変換する時刻関数群が用意されています。これらの時刻関数の相関図を次に示します。
たとえば、time() 関数で取得した現在の時刻(time_t型)を localtime() 関数を用いて struct tm 型に変換することができます。time_t 型の時刻は、ctime() 関数を用いて文字列に変換することができます。
char *ctime(const time_t *clock);
struct tm *localtime(const time_t *clock);
struct tm *gmtime(const time_t *clock);
char *asctime(const struct tm *tm);
乱数
C言語で乱数を生成するには、次の関数を使用します。
int rand(void);
void srand(unsigned int seed );
rand() 関数は 0 から RAND_MAX までの乱数を返します(RAND_MAX はヘッダファイル stdlib.h の中で定義されています)。
ただし、あらかじめ決められた乱数表から順番に値を返しているので、完全にランダムな数を生成しているわけではありません。これは、rand() 関数の戻り値を printf() 関数で出力するプログラムを作成すれば確認できます。実行するたびに毎回同じ数値が出力されるはずです。
そこで乱数の初期化が必要です。乱数の初期化とは、乱数表を先頭からではなく別のところから読み出すようにすることでです。乱数の初期化は srand() 関数で行います。
引数 seed には初期値を指定します。通常は time() 関数の戻り値を指定します。プログラムを実行するたびにそのときの時刻は変わるので、プログラムを実行するたびに異なる乱数が発生するようになります。
#include <time.h> #include <sys/types.h> #include <stdlib.h> int i; srand(time(NULL)); i = rand() % 100;
rand 関数は 0 から RAND_MAX までの乱数を返します。これを 0 から 99 までの乱数にしたいときは、100 で割った余りを使います。
関数で可変個数の引数を扱う
printf() や scanf() では、引数の数や型が不定です。これらの関数のように、引数の個数が可変である関数の作成方法を説明します。
引数の個数が可変の関数を作成するには、ヘッダファイル stdarg.h をインクルードします。
関数の定義は次のようにします。
可変個数の引数は "..." で表します。引数を "..." で表すと、コンパイル時に引数の個数をチェックしなくなります。ただし、可変個数でない引数が最低ひとつは必要です。引数が全て可変個数の引数の関数は作成できません。したがって、次のような関数を定義することはできません。
最初の引数はどんなものでも構いませんが、通常は可変引数の個数や型を示すものが普通です。
関数内では、可変引数を扱うために va_list 型の変数を宣言します。
可変引数の扱いを開始するには、va_start マクロを呼び出します。
変数1 には、va_list 型の変数を指定します。変数2 には、不定引数になる前の最後の固定引数を指定します。
可変の変数を取り出すには va_arg を使用します。
可変引数を扱う必要がなくなったら va_end マクロを呼び出します。
可変個数の引数を扱う関数は何らかの方法で引数の数と型を特定しなければなりません。その方法として、次のようなものが考えられます。
- 型はあらかじめ決まっていて、引数の数を固定引数で渡す。
- 引数の数と型を固定引数で渡す。
- 型はあらかじめ決まっていて、特定の値が現れるまで引数を取り出す。
- va_list型を直接扱う。
型はあらかじめ決まっていて、引数の数を固定引数で渡す
可変個数の引数を扱う関数の例を次に示します。
#include <stdio.h>
#include <stdarg.h>
void print_varargs(int n, ...)
{
va_list pvar;
char *p;
va_start(pvar, n);
for (; n > 0; n--) {
p = va_arg(pvar, char *);
printf("%s\n", p);
}
va_end(pvar);
}
void main(void)
{
print_varargs(1, "hello");
print_varargs(2, "hello", "world");
}
上記の例では、型は char * の固定で、引数の個数を第1引数で与えています。
引数の数と型を固定引数で渡す
printf() でも最初の引数は "%d %s" のように引数の型や個数を表していますね。
型はあらかじめ決まっていて、特定の値が現れるまで引数を取り出す
UNIXのシステムコール execl(), execle(), execlp() のように、特定の値が現れるまでパラメータを取り出します。
可変引数の型が char * で、NULL が現れるまで引数を取り出す例を次に示します。
#include <stdio.h>
#include <stdarg.h>
void print_varargs(int dummy, ...)
{
va_list pvar;
char *p;
va_start(pvar, dummy);
while ((p = va_arg(pvar, char *)) != NULL) {
printf("%s\n", p);
}
va_end(pvar);
}
void main(void)
{
print_varargs(0, "hello", NULL);
print_varargs(0, "hello", "world", NULL);
}
引数の数を渡すのではなく、最後の引数であるという目印として特定の値を決めておく方法です。ただし、最後の引数であることを表す値(上記の例では NULL)を通常値として渡すことはできません。
va_list型を直接扱う
va_list 型の変数を直接扱える関数もあります。
#include <stdio.h> #include <stdarg.h> int vprintf(const char *format, va_list ap); int vfprintf(FILE *stream, const char *format, va_list ap); int vsprintf(char *s, const char *format, va_list ap); int vsnprintf(char *s, size_tn, const char *format, va_list ap);
上記の関数を使えば、引数をひとつひとつ取り出すことなくストリームに出力することができます。
#include <stdio.h>
#include <stdarg.h>
void print_varargs(char *format, ...)
{
va_list pvar;
va_start(pvar, format);
vprintf(format, pvar);
va_end(pvar);
}
void main(void)
{
print_varargs("%s\n", "hello");
print_varargs("%s %s\n", "hello", "world");
}
make
makeはファイルの依存関係を記述したMakeファイルを使って、ターゲットとなるファイルを生成するコマンドです。主にC言語のソース・ファイルをコンパイルおよびリンクするときに使います。
オプション -f が省略されたときは、MakefileまたはmakefileをMakeファイルのパスとします。
引数ターゲットが省略されたときは、Makeファイル中で最初に現れたターゲットを対象とします。
Makeファイルはターゲットの依存関係を以下の構造で記述します。
ターゲット : 依存ファイル
コマンド
生成するファイルのパスを ターゲット に指定します。ターゲットを生成するのに必要なファイルのパスを 依存ファイル に記述します。依存ファイルからターゲットを生成するために実行するコマンドを コマンド に記述します。
makeはターゲットと依存ファイルのタイムスタンプを調べて、以下の場合にターゲットを生成するコマンドを実行します。
- ターゲットが存在しない場合
- ターゲットが依存ファイルより古い場合
Makeファイルの中では、次に示す動的マクロを使用することができます。
- $*
- 現在のターゲットのベース名
- $<
- 依存ファイルの名前
- $@
- 現在のターゲットの名前