C言語でプログラムを作成する場合に参考となる知識,手法等を紹介する.
C言語のプログラムは,いくつかの関数定義の集まりとして表わされるが,呼び出される回数が比較的多く,処理すべき内容が少ない機能的に小さいものは,関数化しないでマクロ定義で表現するのが良い.これにより,関数呼び出しの際に要するオーバヘッド時間を節約することができ,実行効率を上げることができる.
よく使われる汎用的なマクロを以下に示す.
#define Func() do { \ /* declarations */ \ stmt1; \ stmt2; \ /* ... */ \ } while(0) /* (no trailing ; ) */呼び出し側でセミコロンを付けるとき,この展開は単一の文になる. 最適化コンパイラは通常このような常に偽となる条件のテストや分岐を取り除いてくれる.
#define DEBUG(args) (printf("DEBUG: "), printf args) if (n != 0) DEBUG(("n is %d\n", n));
#define ishex(c) (((c) >= '0' && (c) <= '9') || \ ((c) >= 'a' && (c) <= 'f') || \ ((c) >= 'A' && (c) <= 'F')) #define iswhite(c) ( (c) == ' ' || (c) == '\t' || (c) == '\n' ) #define isnum(c) ( isdigit(c) || (c) == '-' )
#define iskanji(c) ((unsigned char)(c) >= 0x81 && \ (unsigned char)(c) <= 0x9f || \ (unsigned char)(c) >= 0xe0 && \ (unsigned char)(c) <= 0xfc) #define iskanji2(c) ((unsigned char)(c) >= 0x40 && \ (unsigned char)(c) <= 0xfc && \ (unsigned char)(c) != 0x7f)
#define skipwhite(s) while(iswhite(*s)) ++s #define skipnwhite(s) while(*s && !iswhite(*s)) ++s
#define lastch(s) s[strlen(s)-1]文字列の終端の改行文字を削除する.
#define rm_ret(s) if (lastch(s) == '\n') lastch(s) = '\0'
#define max(x, y) ( (x) > (y) ? (x) : (y) ) #define min(x, y) ( (x) < (y) ? (x) : (y) )
#define Streq(s1, s2) (strcmp((s1), (s2)) == 0)
#include <limits.h> /* for CHAR_BIT */ #define Mask(bit) (1 << ((bit) % CHAR_BIT)) #define Slot(bit) ((bit) / CHAR_BIT) #define Set(ary, bit) ((ary)[Slot(bit)] |= Mask(bit)) #define Test(ary, bit) ((ary)[Slot(bit)] & Mask(bit))
ヘッダファイルが他のヘッダファイルをインクルードしても型の再定義エラーが起こらないように,すべてのヘッダファイルの先頭と最後に以下のような記述をする.
#ifndef _H_HEADER_ #define _H_HEADER_ // 変数や関数の定義 #endif // _H_HEADER_
これにより,何回インクルードしても最初の1回だけ読み込まれ,再定義エラーにはならない.
デバグメッセージをコード中に埋め込むため
#ifdef DEBUG printf("Debug message\n"); #endif
がよく用いられる.
しかし,上記のコードは可読性を悪くする.すなわち,移植性を確保するために#defineを行の左端に置くことにより,字下げ(インデント)が乱されてしまう. また,各メッセージを表示するのに3行を使う. これは,以下のようなマクロを使うことにより改善できる.
#ifdef DEBUG #define DBG(x) x #else #define DBG(x) #endif
上記では,DEBUGが定義されていると,DBG(x)というマクロがその引数に展開される.
また,DEBUGが定義されていないとマクロDBG(x)はnull文字列になり,DBG(x)が無視されるようになる.
マクロの引数は合法的なCの文や式ならば何でもよい.
但し,マクロを起動する文の後にセミコロンを書いてはいけない.セミコロンがあると,マクロがnullに展開された場合,if--elseの対応を狂わせることがある.
例えば,以下のように使用する.
DBG(printf("Debug start : %d\n", code);)
プログラムで使われる技法のいくつかを示す.
#include <stddef.h> /* for NULL, size_t */ #include <stdarg.h> /* for va_ stuff */ #include <string.h> /* for strcat et al */ #include <stdlib.h> /* for malloc */ char *vstrcat(char *first, ...) { size_t len = 0; char *retbuf; va_list argp; char *p; if (first == NULL) return NULL; len = strlen(first); va_start(argp, first); while ((p = va_arg(argp, char *)) != NULL) len += strlen(p); va_end(argp); retbuf = malloc (len + 1); /* +1 for trailing \0 */ if (retbuf == NULL) return NULL; /* error */ (void)strcpy(retbuf, first); va_start(argp, first); while ((p = va_arg(argp, char *)) != NULL) (void)strcat(retbuf, p); va_end(argp); return retbuf; }この関数の使用例を以下に示す.呼び出し側はmallocで確保された戻り値のメモリ領域を使用後解放しなければならない.
char *str = vstrcat("Hello, ", "world!", (char *)NULL);
int function1(), function2(); struct { char *name; int (*funcptr)(); } symtab[] = { "function1", function1, "function2", function2, };
struct name { int namelen; char *name; };これらは,name型の宣言を以下のようにすると1回で済ますことができる.
struct name { int namelen; char name[1]; };name型のデータを確保するためには,name型と実際のname(aName)の合計サイズを計算してその大きさをmallocで確保する.
p = (char *)malloc(sizeof(name) + strlen(aName)); strcpy(p->name, aName);
int **array = (int **)malloc(nrows * sizeof(int *)); for (i = 0; i < nrows; i++) array[i] = (int *)malloc(ncolumns * sizeof(int));配列の内容を連続領域に確保したい場合は,以下のようにする.
int **array = (int **)malloc(nrows * sizeof(int *)); array[0] = (int *)malloc(nrows * ncolumns * sizeof(int)); for (i = 1; i < nrows; i++) array[i] = array[0] + i * ncolumns;どちらの場合も通常の配列と同様にarray[i][j]でその内容をアクセスすることができる. また,2次元配列を1次元配列で以下のように疑似することができる.
int *array = (int *)malloc(nrows * ncolumns * sizeof(int));この場合は,array[i * ncolums + j]で配列の(i, j)要素をアクセスする必要がある. なお,上記のいずれにおいても配列が不用になったときに配列領域を解放するのを忘れないようにする必要がある.
fd = open(filename, O_WRONLY, 0666); close(1); dup(fd); close(fd);上記により,以後の標準出力への書き込みはfdで指定されるファイルへ書き込まれる.
fpin = popen(command1, "r"); fpout = popen(command2, "w"); while ((c = getc(fpin)) != EOF) putc(c, fpout); pclose(fpin); pclose(fpout);