最後に、C言語のさまざまな関数をおさらいしておきましょう。
#include <stdio.h> int printf( const char *format, ... ); int fprintf( FILE *stream, const char *format, ... ); int sprintf( char *str, const char *format, ... ); int snprintf( char *str, size_t size, const char *format, ... ); #include <stdarg.h> int vprintf( const char *format, va_list ap ); int vfprintf( FILE *stream, const char *format, va_list ap ); int vsprintf( char *str, char *format, va_list ap ); int vsnprintf( char *str, size_t size, const char *format, va_list ap );
おなじみ printf 関数とその家族たち。
printf関数は書式制御文字列 format に従って任意の個数のデータを
文字列として標準出力に出力します。返り値は出力した文字数です。
fprintf関数は指定のストリームに文字列を出力します。
sprintf関数は指定のメモリに文字列を出力します。
メモリが溢れても関知しません。
snprintf関数は指定のメモリに文字列を出力します。
最大出力文字数を指定でき、メモリ溢れを防止できます。
vprintf関数は可変引数リストを受け取り、標準出力に出力します。
vfprintf関数は可変引数リストを受け取り、指定のストリームに出力します。
vsprintf関数は可変引数リストを受け取り、指定のメモリに出力します。
メモリ溢れを関知しません。
vsnprintf関数は可変引数リストを受け取り、指定のメモリに出力します。
最大出力文字数を指定でき、メモリ溢れを防止します。
int値の出力の色々
double値の出力の色々int i=255; printf("[%d] [%5d] [%-5d] [%+5d] [%05d] [% d]\n", i, i, i, i, i, i ); printf("[%d] [%5d] [%-5d] [%+5d] [%05d] [% d]\n", -i, -i, -i, -i, -i, -i ); // [255] [ 255] [255 ] [ +255] [00255] [ 255] // [-255] [ -255] [-255 ] [ -255] [-0255] [-255]
char*値の出力の色々double d=3.141592653; printf("[%f] [%5.3f] [%7.3f] [%e]\n", d, d, d, d ); // [3.141593] [3.142] [ 3.142] [3.141593e+00]
進数の色々char s[32]="Physics"; printf("[%s] [%10s] [%-10s] [%4s] [%10.4s]\n", s, s, s, s, s ); // [Physics] [ Physics] [Physics ] [Physics] [ Phys]
出力フィールドの変数による指定int i=255; printf("%d %o %x %X %#x %#X\n", i, i, i, i, i ); // 255 377 ff FF 0xff 0XFF
double d=3.141592653; int i=6, j=4; printf("%*.*f\n", i, j, d ); // 3.1416
printf関数の引数の暗黙の型変換
char | → | int |
short | → | int |
u_char | → | u_int |
u_short | → | u_int |
float | → | double |
signed, unsignedでの混乱の例
数値255の実体は 0xff、これを符合付の char で解釈すると -1 になります。 なのでchar型変数 c は -1 となる。printfに渡される際に int型に暗黙にキャスト変換されるのでこれはint型の-1となります。 int型の-1の実体は 0xffffffff です。 これを %dで表示すると -1 となり、%x で表示すると ffffffff、%uで表示すると 4294967295 となり、最初に指定した値とはまるで 違う値が表示されるように見えます。char c = 255; printf("%d %x %u\n", c, c, c ); // -1 ffffffff 4294967295
暗黙の型変換の規則により u_char の値は u_int に変換されるので、 char型の 0xffの値は u_int 型の 0x000000ff に変換され、期待通りに表示されます。 %dと%uで同じ表示になったのはこの値の実体が int でも u_int でも同じだから で、もっと巨大な値だったら異なります。char c = 255; printf("%d %x %u\n", (u_char)c, (u_char)c, (u_char)c ); // 255 ff 255
fprintfの使用例
stderrに出力するとバッファリングされずに直ちに表示されます。 stdoutに出力するとバッファリングされて遅れて表示されます。 直ちに表示させるには fflush(stdout)を唱えます。fprintf( fptr, "hello, world"); fprintf( stderr, "hello, world"); fprintf( stdout, "hello, world");
sprintfの使用例
sprintfは strcpyや strcat より遥かに便利でしかも文字数が返ってきます。 ただしバッファ溢れに注意、最悪の場合にはセキュリュティーホールにもなります。char str[32], strA[32], strB[32]; sprintf( str, "%s%s", strA, strB ); int month, day; char fname[128]; int len = sprintf( fname, "access%02d%2d.dat", month, day );
snprintfの使用例
バッファ溢れの心配がありません。snprintfを備えていないOSもあります。 sizeof演算子を配列名に演算すれば配列のサイズになりますが、 ポインタに演算したらポインタ変数の大きさ 4 になることに注意。char str[32], strA[32], strB[32]; int len = snprintf( str, sizeof(str), "%s%s", strA, strB );
vprintf関数の使用例、可変引数を受ける関数でそれら引数を表示できます。
#include <stdio.h> #include <stdarg.h> int func( const char* format, ... ) { va_list argptr; va_start( argptr, format ); int len = vprintf( format, argptr ); return len; } int main() { int a, b, c; func("%d %d %d\n", a, b, c ); }
vfprintf関数の使用例
int func( FILE* fptr, const char* format, ... ) { va_list argptr; va_start( argptr, format ); int len = vfprintf( fptr, format, argptr ); return len; }
vsprintf関数の使用例
int func( const char* format, ... ) { va_list argptr; va_start( argptr, format ); char buf[256]: int len = vsprintf( buf, format, argptr ); puts(buf); return len; }
vsnprintf関数の使用例
int func( const char* format, ... ) { va_list argptr; va_start( argptr, format ); char buf[256]: int len = vsnprintf( buf, sizeof(buf), format, argptr ); puts(buf); return len; }
#include <stdio.h> int scanf( const char *format, ... ) int fscanf( FILE *stream, const char *format, ... ) int sscanf( const char *str, const char *format, ... ) #include <stdarg.h> int vscanf( const char *format, va_list ap ) int vsscanf( const char *str, const char *format, va_list ap ) int vfscanf( FILE *stream, const char *format, va_list ap )
おなじみ scanf 関数とその家族たち。
scanf関数は標準入力から読み込んだ文字列を書式制御文字列 format に従って
指定形式で指定アドレスにデータを格納します。指定のデータが正しく読み込まれれば
scanfは入力バッファから読み込んだデータを除くが、
入力データに指定以外のゴミデータが混じっていると種々のトラブルを生じる。
返り値は正しくデータを格納できた変数の数。
fscanf関数は指定のファイルストリームから文字列を読み込み、指定の形式で
データを格納します。
入力データに指定以外のゴミデータが混じっていると種々のトラブルを生じる。
sscanf関数は指定のメモリから文字列を読み込み、指定の形式でデータを格納します。
読み込み元のメモリには何ら書き込みを行わない。
入力データに指定以外のゴミデータが混じっていてもトラブルを生じない。
vscanf関数は標準入力から入力したデータを可変引数リストで指定される
アドレスに格納します。
vfscanf関数は指定のストリームから入力したデータを可変引数リストで指定される
アドレスに格納します。
vsscanf関数は指定のメモリから入力したデータを可変引数リストで指定される
アドレスに格納します。
scanf関数の書式制御はprintf関数のそれに似ているが根本的に異なり、 さらに高機能である。printfの引数はキャスト変換されてint,u_int,doubleと 大雑把に整えられて printf関数に渡されるので書式制御も%d %u %fと大雑把で良いが、 scanfの引数はアドレスだけが渡されて scanf関数はそのアドレス上にある変数の型を知ることができないので、 書式制御文字列 format でその型を具体的に明示して指定しなければならない。
種々の数値データの読み込み
文字、複数文字、文字列の読み込みshort s; int i; long l; scanf("%hd %d %ld",&s, &i, &l ); u_short us; u_int ui; u_long ul; scanf("%hu %u %lu", &s, &i, &l ); float f; double d; scanf("%f %lf", &f, &d );
%8cは空白も含めて8文字読み込み、NULLを付加しない。 %sは空白があるまで読み込みNULLを付加します。char c; char cs[8]; char str[16]; scanf("%c %8c %s", &c, cs, str );
読み込みの制限、ガイド
char str[16]; scanf("%*s %15s", str ); int a, b, c; scanf("%d : %d : %d", &a, &b, &c );
文字列読み込みの色々
char str[16]; scanf("%[a-z]", str ); // 英小文字が続く限り読み込む scanf("%[a-zA-Z0-9]", str ); //英数が続く限り読み込む scanf("%[^#%]", str ); // #か%が現れるまで読み込む scanf("%[^\n]", str ); // 改行が現れるまで読み込む scanf("%*[^=]=%[^;]; %*[^=]=%[^;];", str1, str2 ); // name1=value1; name2=value2; のvalueだけ読み込む例
scanf関数の正しい使い方
scanf関数は書式制御に合わないデータが入力されると それをバッファに放置するので種々の混乱を起こす。その対策は scanfの返り値を検証して、異常ならバッファをクリアします。 例えば整数を3つづつ読み込み続けるには
これは面倒なので fgets と sscanfを使う方が楽です。int a, b, c; int status; while( (status=scanf("%d %d %d", &a, &b, &c )) != EOF ){ if( status != 3 ){ fprintf( stderr, "Format error, ignored.\n" ); rewind(stdin); // バッファに残ったデータを捨てる } printf("Read %d %d %d\n", a, b, c ); }
fscanfの使用例
fscanf( fptr, "%d", &i );
sscanfの使用例
int a, b, c; char buf[256]; while( fgets( buf, sizeof(buf), fptr ) ){ if( 3 != sscanf( buf, "%d %d %d", &a, &b, &c ) ){ fprintf( stderr, "Format error, ignored.\n"); } printf("Read %d %d %d\n", a, b, c ); }
vscanf関数の使用例
#include <stdio.h> #include <stdarg.h> int func( const char* format, ... ) { va_list argptr; va_start( argptr, format ); int terms = vscanf( format, argptr ); return terms; } int main() { int a, b, c; func("%d %d %d", &a, &b, &c ); }
vfscanf関数の使用例
int func( FILE* fptr, const char* format, ... ) { va_list argptr; va_start( argptr, format ); int terms = vscanf( fptr, format, argptr ); return terms; }
vsscanf関数の使用例
int func( const char* format, ... ) { va_list argptr; va_start( argptr, format ); char buf[256]; fgets( buf, sizeof(buf), stdin ); int terms = vsscanf( buf, format, argptr ); return terms; }
#include <string.h> char* strcpy(char* dst, const char* src) char* strncpy(char* dst, const char* src, size_t count) char* strcat(char* s, const char* append) char* strncat(char* s, const char* append, size_t count) int strcmp(const char* s1, const char* s2) int strncmp(const char* s1, const char* s2, size_t count) char* strchr(const char* s, int c) char* strrchr(const char* s, int c) char* strpbrk(const char* s, const char* charset) size_t strspn(const char* s, const char* charset) size_t strcspn(const char* s, const char* charset) char* strstr(const char* big, const char* little) char* strtok(char* s, const char* delim) size_t strlen(const char* s) char* strerror(int errno)
strcpy,strcatの使用例。
strcpy,strcatを使うよりsprintf関数を使う方が簡潔であることが多いです。char str1[32], str2[32], str3[32]; strcpy( str1, "hello," ); strcpy( str2, "world!" ); strcpy( str3, str1 ); strcat( str3, str2 );
sprintf( str3, "%s%s", str1, str2 );
strcmp, strncmp の使用例。
if( !strcmp( str1, str2 ) ){ // match! }else{ // mismatch! } if( !strncmp( str, "abc", 3 ) ){ // match! }else{ // mismatch! }
strchr,strrchrの使用例。
char str[] = "naoki@cms.phys.s.u-tokyo.ac.jp"; char* ptr = strchr( str, '@' ); if( !ptr ){ fprintf( stderr, "domain part is missing.\n" ); exit(1); } printf("domain=%s\n", ++ptr );
char str[] = "naoki@cms.phys.s.u-tokyo.ac.jp"; char* ptr = strrchr( str, '.' ); if( !ptr ){ fprintf( stderr, "nation's code is missing.\n" ); exit(1); } printf("nation=%s\n", ++ptr );
strpbrkの使用例。
char str[] = "abcdefg # comment"; char* ptr = strpbrk( str, "#%!;" ); if( ptr ) *ptr = '\0'; printf("%s\n", ptr ); // any kind of comments are elliminated.
strspn,strcspnの使用例。
char str[] = "-=-=-=-=-=-=-=-=-=-=- abcdefg"; int len = strspn( str, "-=" ); printf("%d\n", len );
char str[] = "abcdefg # comment"; int len = strcspn( str, "#%!;" ); str[len] = '\0'; printf("[%s]\n", str );
strstrの使用例。
char* ptr = strstr( str, key ); if( ptr ){ printf("found %s in %s.\n", key, str ); }
strtokの使用例。
strtokには1回目の呼出の時だけアドレスを渡して、以降はNULLを渡します。 strtokは受け取ったアドレスに書き込むを行うので、文字列は書き込み可能な メモリ上になければなりません。strtokは空の文節を返せません。char str[] = "sin,cos,tan,sqrt,,log"; char *ptr; for( ptr=strtok( str, "," ); ptr; ptr=strtok( NULL, "," ) ){ printf("[%s]\n", ptr ); }
strlenの使用例。
char str[] = "abcde"; printf("len=%d\n", strlen(str) ); // 5 not count the null terminator
strerrorの使用例。
extern int errno; FILE* fptr = fopen( fname, "r" ); if( !fptr ){ printf("error in fopen: %s\n", strerror(errno) ); }
#include <stdlib.h> int getopt( int argc, char** argv, const char* list )
getopt関数は main関数が受け取るコマンドオプションを 解析するのに大変便利な関数です。 例えば、以下のようなコマンドオプションが与えられるとします。
これらの引数をオプションとして処理するには以下のようにします。a.out -a -b -c file.dat -def
getopt関数の使用例
つまりgetopt関数の第3引数に受け付け可能なオプション文字列のリストを 指定します。コロン':'が後続した文字のオプションには例のように オプション引数が付くことになります。#include <stdio.h> #include <stdlib.h> int main( int argc, char* argv[] ) { int ch; extern char *optarg; while( (ch=getopt(argc, argv, "abc:def")) != EOF ){ switch( ch ){ case 'a' : printf("Option %c.\n", ch ); break; case 'b' : printf("Option %c.\n", ch ); break; case 'c' : printf("Option %c with arg %s.\n", ch, optarg ); break; case 'd' : printf("Option %c.\n", ch ); break; case 'e' : printf("Option %c.\n", ch ); break; case 'f' : printf("Option %c.\n", ch ); break; default : break; } } }
コマンド引数を全部調べ終えるか、 ダッシュ'-'で始まらない項目があると getopt関数は EOF を返します。 残ったオプション以外のコマンド引数は自分で解析します。 以下のように調整すると、残ったコマンド引数を探しやすくなります。extern int opterr; opterr=0;
同じプログラム中で、別なポインタ配列をコマンド引数と同様に getopt関数を使って解析したいなら、その使用前に以下の初期化作業を 行っておかなければなりません。extern int optind; argc -= optind; argv += optind;
extern int optreset; extern int optind; optreset = 1; optind = 1;
#include <sys/types.h> #include <regex.h> int regcomp( regex_t *preg, const char *pattern, int cflags) int regexec( const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags ) size_t regerror( int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size ) void regfree( regex_t *preg )
これらの関数は grep や egrep コマンドのように 文章中の特定のパターンの文字列を検索します。 パターンの指定方法のことを一般に正規表現と呼び、その文法には色々な流派が あります。それは grep と egrep でも異なります。ここでは egrep コマンドで使われる「拡張された正規表現」の文法で解説します。
regcomp関数は文字列として与えられた正規表現を 内部処理用形式に変換します。regex_t型の変数がその内部表現を 管理します。regcomp関数にはその変数のアドレスを指定します。
以後この変数 preg を記述子として正規表現を指定します。 第3引数には正規表現の解釈を微妙に変更させるパラメタを指定します。 上記の指定で拡張された正規表現を扱うようになり、 さらに行末を認識できるようになります。ここで 使用できるパラメタは以下の通りです。regex_t preg; regcomp( &preg, "abc[0-9]", REG_NEWLINE|REG_EXTENDED );
regexec関数は与えられた文章中に正規表現にマッチする 文字列がないかを検査します。文章の先頭から検索して1つを見つけると、 その文字列の先頭と末尾の直後の文章中での位置を返します。
検索結果は第4引数に指定した regmatch_t 型の配列変数に 格納されますが、普通の使用ではその第0要素だけで十分です。 そのメンバ変数 rm_so, rm_eo にマッチした文字列の 先頭と末尾の直後の文章中での位置が格納されています。ただし この型は long long型のため intにキャスト変換しないと扱えません。regmatch_t pmatch[1]; if( regexec( &preg, buf, 1, pmatch, 0 ) == 0 ){ printf("%d %d\n", (int)pmatch[0].rm_so, (int)pmatch[0].rm_eo ); }
同じ文章の後続をさらに検索するには再びregexec関数を呼びますが、 検索開始位置をずらして、さらにその位置が行頭ではないことを 指示するために、第2引数のアドレスを rm_eo の位置にずらして、 第5引数には REG_NOTBOL を指定します。
char* ptr = buf + pmatch[0].rm_eo; if( regexec( &preg, ptr, 1, pmatch, REG_NOTBOL ) == 0 ){ printf("%d %d\n", (int)pmatch[0].rm_so, (int)pmatch[0].rm_eo ); }
正規表現の部分構造がマッチした部分を調べるには次のように 配列pmatchの第1、第2要素もチェックします。
regmatch_t pmatch[4]; if( regexec( &preg, buf, 4, pmatch, 0 ) == 0 ){ for( int i=0; i<preg.re_nsub; i++ ){ printf("%d %d\n", (int)pmatch[i].rm_so, (int)pmatch[i].rm_eo ); } }
regexec関数の第5引数に指定するパラメタには 以下の3種があります。
そもそもegrepの正規表現を知らない人が多いでしょう。 これはUNIXのシェルやDOSのcommand.comが解釈するワイルドカード とは全く異なるものです。egrepの正規表現、すなわち、 「拡張された正規表現」の文法をまずしっかり学んでおきましょう。
ここで簡単に拡張された正規表現について解説します。 詳しくは egrep のマニュアルを参照してください。
さらに、0-9, a-zなどを表す以下の表現もあります。
[:alnum:], [:alpha:], [:cntrl:], [:digit:], [:graph:], [:lower:], [:print:], [:punct:], [:space:], [:upper:], [:xdigit:]これらの範囲は ctype.h の is?関数ファミリーと同じ。 正規表現でこれらを使うには例えば [[:alnum:]]、[^[:alnum:]] とします。
正規表現の文法を学ぶには以下のプログラムを利用すると良いでしょう。
このプログラム regex を以下のようにして使います。// regex.cc #include <sys/types.h> #include <regex.h> #include <stdio.h> int main( int argc, char* argv[] ) { regex_t preg; regmatch_t pmatch[1]; char buf[256]; regcomp( &preg, argv[1], REG_NEWLINE|REG_EXTENDED ); while( fgets( buf, sizeof(buf), stdin ) ){ if( regexec( &preg, buf, 1, pmatch, 0 ) ) continue; printf("%s", buf ); char* ptr = buf; do{ if( (int)pmatch[0].rm_eo == 0 ){ printf(" "); ptr++; }else{ printf("%*.*s", (int)pmatch[0].rm_eo, (int)pmatch[0].rm_eo - (int)pmatch[0].rm_so, "+*********************************************" "**********************************************" ); ptr += pmatch[0].rm_eo; } }while( *ptr && regexec( &preg, ptr, 1, pmatch, REG_NOTBOL ) == 0 ); printf("\n"); } regfree( &preg ); return 0; }
ファイルの各行の正規表現にマッチする部分に +*** のマークが付きます。regex 'abc' < file
CShellのファイルに対するパターン( *, ? など) は 正規表現とは異なります。ですが若干の修正でファイル名の パターン置換を正規表現で行えるようになります。
文頭文末を表す正規表現^,$を付加することを忘れずに。
パターン 正規表現 ? . * .* [a-z] [a-z] {str1,str2} (str1|str2) ~naoki 該当なし
パターンを正規表現に変換するルーチンは以下のようになるでしょう。 ただし ~の処理は省略しました。
~の展開には getpwnam(), getlogin() 関数などを使いますが ファイルのマッチを検査する場合に ~が登場することは考えなくても 良いでしょう。int Pattern_to_Regex( char* pattern, char* regex, int len ) { int i, j=0; int braces=0; regex[j++] = '^'; for( i=0; pattern[i] && j<len; i++ ){ switch( pattern[i] ){ case '?' : regex[j++] = '.'; break; case '*' : regex[j++] = '.'; regex[j++] = '*'; break; case '{' : regex[j++] = '('; braces=1; break; case '}' : regex[j++] = ')'; braces=0; break; case ',' : regex[j++] = (braces ? '|' : ','); break; default : regex[j++] = pattern[i]; break; } } regex[j++] = '$'; regex[j++] = '\0'; if( j>len ) return 1; return 0; }
#include <stdio.h> FILE* fopen( char *path, char *mode ) FILE* fdopen( int fildes, char *mode ) int fclose( FILE *stream ) int fileno( FILE *stream )
mode | 用途 | 開始位置 | 動作 |
"r" | 読み込み用 | 先頭 | ファイルが無ければ失敗 |
"r+" | 読み書き両用 | 先頭 | 有ればそのままにする |
"w" | 書き込み用 | 先頭 | ファイルが無ければ新設 |
"w+" | 読み書き両用 | 先頭 | 有れば空にする |
"a" | 追加書き込み用 | 終端 | ファイルが無ければ新設 |
"a+" | 読み書き両用 | 終端 | 有ればそのままにする |
fdopen関数は既に低水準でファイルが開いている場合に、 それを高水準でも扱えるように、その descriptor を扱う file stream を 作成します。
fileno関数は file stream の核をなす file descriptor の番号を返します。
#include <stdio.h> int fputs(const char *str, FILE *stream) int puts(const char *str) char* fgets( char *str, size_t size, FILE *stream ) char* gets( char *str )
puts関数は改行を余計に出力するが fputs関数はしません。
fgets関数は改行も読み込むが gets関数はしません。
gets関数はバッファあふれを起こし、セキュリュティーホールとなるので 絶対に用いてはなりません。代わりにfgetsを使います。
char buf[256]; while( fgets( buf, sizeof(buf), stdin ) ){ fputs( buf, stdout ); }
#include <stdio.h> int fread(void *ptr, size_t size, size_t nmemb, FILE *stream) int fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
fread,fwriteはエラーが起こらない限り、指定の量だけ処理するまで戻りません。
fread,fwrite関数の使用例
double d[128]; while( 128 != fread( d, sizeof(double), 128, fptr1 ) ){ fwrite( d, sizeof(double), 128, fptr2 ) ); }
#include <stdio.h> int fseek(FILE *stream, long offset, int whence) void rewind(FILE *stream) long ftell(FILE *stream) int fflush(FILE *stream)
whenceに指定する値 SEEK_SET, SEEK_CUR, SEEK_END のいずれか。
rewindは(void)fseek(stream, 0L, SEEK_SET)に同じ。rewindをstdinに
使うと読み込まれたバッファがクリアされます。
fseek関数の使用例
int a; int status; while( (status=scanf("%d", &a )) != EOF ){ if( status != 1 ) rewind(stdin); printf("a=%d\n", a ); }
fflushは出力バッファに溜っているものを直ちに出力させます。
flush関数の使用例
printf("I am sleepy, but..."); fflush(stdout);
多くの読み込み関数はファイル終端による読み込み失敗と エラーによる読み込み失敗を区別しません。それを確認する関数。int feof(FILE *stream) int ferror(FILE *stream)
feof,ferror関数の使用例
if( feof(fptr) ){ puts("reached the end"); } if( ferror(fptr) ){ puts("error occurred, may be file has already been closed."); }
サイズ不明の大量のデータを格納するために動的にメモリを確保するよりも 一時的なファイルに書き込む方がプログラムは遥かに簡単です。 これらの関数はその一時的なファイルを作成したり、 そのファイル名を用意してくれます。複数のプロセスが同時に走っていても これらのテンポラルファイルが重ならないようにとりはかってくれます。#include <stdio.h> FILE* tmpfile(void) char* tmpnam(char *str) char* tempnam(const char *tmpdir, const char *prefix)
tmpfile関数は、テンポラルファイルを /var/tmp ディレクトリの下あたりに適当な名前で作成して、そのstreamを返します。 ファイルは読み書き両用モード(w+)で開かれています。テンポラルファイルに データを書き終えた後、fflush,rewindでファイル先頭から読み直すことができます。 fcloseでテンポラルファイルは消去されます。
tmpfile関数の使用例
FILE* tmp = tmpfile(); while( some conditions ){ fputs( something, tmp ); } fflush(tmp); // 書き込みの徹底 rewind(tmp); // 巻き戻し while( fgets( buf, sizeof(buf), tmp ) ){ do_something(buf); } fclose(tmp);
tmpnam 関数はテンポラルファイルの名前だけを作るための関数です。 引数にファイル名の出だしの文字列を与えておくと、それを元に テンポラルファイルとして相応しいファイル名がその文字列に上書きされます。 なので文字列のメモリは十分長くとっておかないとなりません。 また、この関数はファイルは開きませんので自分でfopenしなくてはなりません。 もたもたしていると別なプロセスに開けられてしまうかも知れません。
tempnam関数は、テンポラルファイルのディレクトリも指定できます。 さらに動的にメモリを確保してテンポラルファイルの名前を格納します。 この関数もファイルは開きませんので自分でfopenしなくてはなりません。 もたもたしていると別なプロセスに開けられてしまうかも知れません。
上記の理由で tmpnam関数と tempnam 関数は テンポラルファイルの作成には不十分です。
tmpnam関数の使用例
char tmpfile[L_tmpnam] = "abc"; tmpnam( tmpfile ); FILE* tmp = fopen( tmpfile, "w+" ); do_something(); fclose(tmp); unlink(tmpfile);
tempnam関数の使用例
char* tmpfile = tempnam( "/var/tmp", "abc" ); FILE* tmp = fopen( tmpfile, "w+" ); do_something(); fclose(tmp); unling(tmpfile); free(tmpfile);
#include <fcntl.h> int open(const char *path, int flags, mode_t mode)
読み込み用にオープン
int fd = open( fname, O_RDONLY );
上書き込み用にオープン、ファイルが無ければエラーにする。
int fd = open( fname, O_WRONLY );
書き込み用にオープン、ファイルが無ければ自分だけ読み書きできるように新設する。
int fd = open( fname, O_WRONLY|O_CREAT, 0600 );
書き込み用にオープン、ファイルが無ければ自分だけ読み書きできるように新設、 有れば空にする。
int fd = open( fname, O_WRONLY|O_TRUNC|O_CREAT, 0600 );
書き込み用にオープン、ファイルが無ければ自分だけ読み書きできるように新設、 有ればエラーにする。
int fd = open( fname, O_WRONLY|O_EXCL|O_CREAT, 0600 );
追加書き込み用にオープン、ファイルが無ければエラーにする。
int fd = open( fname, O_APPEND );
追加書き込み用にオープン、ファイルが無ければ自分だけ読み書きできるように新設
int fd = open( fname, O_APPEND|O_CREAT, 0600 );
読み書き共用にオープン、ファイルが無ければエラーにする。
int fd = open( fname, O_RDWR );
読み書き共用にオープン、ファイルが無ければ自分だけ読み書きできるように新設
int fd = open( fname, O_RDWR|O_CREAT, 0600 );
#include <sys/file.h> int flock( int fd, int operation )
flock関数はBSD派生OSのみ。他のUNIXでは lockf関数を使います。
ファイルに排他ロックをかけます。既にロックが掛かっていれば待機します。
flock( fd, LOCK_EX );
ファイルに排他ロックをかけます。既にロックが掛かっていれば待たずに 戻りアクセスできます。
flock( fd, LOCK_EX|LOCK_NB );
ファイルに共有ロックをかけます。既に共有ロックが掛かっていれば 待たずに戻り、アクセスでます。既に排他ロックが掛かっていれば待機します。
flock( fd, LOCK_SH );
ファイルのロックを解除する。単にクローズするだけでも解除できます。
flock( fd, LOCK_UN );
#include <sys/types.h> #include <sys/uio.h> #include <unistd.h> ssize_t read(int d, void *buf, size_t nbytes) ssize_t write(int d, const void *buf, size_t nbytes) off_t lseek(int fildes, off_t offset, int whence)
readは指定量以下でもデータの流れるタイミングによって 処理を中断して返ることが有るので、必ず返り値を検査しなくてはなりません。
read関数の使用例
char buf[1024*1024]; int left = sizeof(buf); int cur = 0; int len; while( (len=read( fd, &buf[cur], left )) > 0 ){ cur += len; left -= len; }
writeはエラーが起こらない限り指定量処理し終るまで返りません。
lseekのwhenceに指定する値は SEEK_SET, SEEK_CUR, SEEK_END。
MS-DOSには getch, kbhit 関数があり、 押されたキーの文字を即座にプログラムで処理できたり、 キーが押されているかを検査できたりしました。 しかし、これらの関数は UNIX にはありません。 しかし UNIXは入出力の詳細を多岐に渡って細かく制御でき、 getch, kbhit と同等のことを自分で作成することができます。
UNIXでの getch関数の作り方。
標準入力を noncanonical input mode に変更し、入力を1文字だけ待つようにする。
さらに入力文字が表示されないようにする。
このgetch関数の使用前に begin_getch関数で標準入力の設定を変更し、 使用後には end_getch関数で設定を元に戻します。#include <stdio.h> #include <unistd.h> #include <termios.h> static struct termios t_orig; void begin_getch( void ) { struct termios t; tcgetattr( 0, &t ); t_orig = t; t.c_lflag &= ~(ICANON|ECHO); t.c_cc[VMIN] = 1; tcsetattr( 0, TCSADRAIN, &t ); } char getch( void ) { char c; int status = read( 0, &c, sizeof(char) ); return status ? c : (char)status; } void end_getch( void ) { tcsetattr( 0, TCSADRAIN, &t_orig ); } main() { char c; begin_getch(); while(1){ c = getch(); printf( "Hit %c\n", c ); } end_getch(); }
UNIXでの kbhit関数の作り方
標準入力を noncanonical input mode に変更し、入力を待たないようにする。 さらに入力文字が表示されないようにする。このkbhit関数の使用前に begin_kbhit関数で標準入力の設定を変更し、 使用後には end_kbhit関数で設定を元に戻します。#include <stdio.h> #include <unistd.h> #include <termios.h> static struct termios t_orig; void begin_kbhit( void ) { struct termios t; tcgetattr( 0, &t ); t_orig = t; t.c_lflag &= ~(ICANON|ECHO); t.c_cc[VMIN] = 0; tcsetattr( 0, TCSADRAIN, &t ); } char kbhit( void ) { char c; int status = read( 0, &c, sizeof(char) ); return status ? c : (char)status; } void end_kbhit( void ) { tcsetattr( 0, TCSADRAIN, &t_orig ); } main() { char c; begin_kbhit(); while(1){ if( c = kbhit() ){ printf("Hit %c\n", c ); }else{ printf("No hit\n"); sleep(1); // このループが猛回転するのを防ぐため。 } } end_kbhit(); }
#include <sys/types.h> #include <sys/time.h> #include <time.h> typedef time_t long; // これ以外の定義もある struct tm { int tm_sec; /* seconds (0 - 60) */ int tm_min; /* minutes (0 - 59) */ int tm_hour; /* hours (0 - 23) */ int tm_mday; /* day of month (1 - 31) */ int tm_mon; /* month of year (0 - 11) */ int tm_year; /* year - 1900 */ int tm_wday; /* day of week (Sunday = 0) */ int tm_yday; /* day of year (0 - 365) */ int tm_isdst; /* is summer time in effect? */ char *tm_zone; /* abbreviation of timezone name */ long tm_gmtoff; /* offset from UTC in seconds */ }; time_t time(time_t *tloc) 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) time_t mktime(struct tm *tm) double difftime(time_t time1, time_t time0) size_t strftime(char *buf, size_t maxsize, const char *format, const struct tm *timeptr) int gettimeofday( struct timeval* tp, struct timezone* tzp )
UNIXでは時刻はグリニッジ時 1970年1月1日0時0分0秒 からの 通算した秒で管理されています。 その値を扱う型 time_t は long型と同じ。なので約21億秒まで数えられます。 この時は2038年1月19日3時14分7秒。この時がUNIXの終りとも言われています。 time_t型を u_longにすればもっともつだろうに。
時刻関連の関数の使用例
time_t t = time(NULL); printf("%ld\n", t ); printf("%s\n", ctime(&t) ); struct tm* tm; tm = localtime(&t); printf("JST: %d %d %d\n", tm->tm_hour, tm->tm_min, tm->tm_sec ); tm = gmtime(&t); printf("GMT: %d %d %d\n", tm->tm_hour, tm->tm_min, tm->tm_sec ); struct tm tm2000; tm2000.tm_sec = 0; tm2000.tm_min = 0; tm2000.tm_hour= 0; tm2000.tm_mday= 1; tm2000.tm_mon = 0; // January tm2000.tm_year= 100; // 2000 tm2000.tm_wday= 0; tm2000.tm_yday= 0; tm2000.tm_isdst = 0; printf("%s\n", asctime(&tm2000) ); time_t t2000 = mktime(&tm2000); printf("%f\n", difftime(t,t2000) ); char str[32]; strftime( str, sizeof(str), "%d/%b/%Y:%H:%M:%S", tm ); printf("%s\n", str ); struct timeval tv; gettimeofday( &tv, NULL ); printf("%d.%d\n", tv.tv_sec, tv.tv_usec );
tm構造体中では 1月が0番月となることに注意。
strftimeの書式制御には以下のように色々な表現があります。
以下は FreeBSDでの man strftime からの抜粋。
%A is replaced by national representation of the full weekday name. %a is replaced by national representation of the abbreviated weekday name, where the abbreviation is the first three characters. %B is replaced by national representation of the full month name. %b is replaced by national representation of the abbreviated month name, where the abbreviation is the first three characters. %C is replaced by (year / 100) as decimal number; single digits are preceded by a zero. %c is replaced by national representation of time and date (the format is similar with produced by asctime(3)). %D is equivalent to ``%m/%d/%y''. %d is replaced by the day of the month as a decimal number (01-31). %E* %O* POSIX locale extensions. The sequences %Ec %EC %Ex %EX %Ey %EY %Od %Oe %OH %OI %Om %OM %OS %Ou %OU %OV %Ow %OW %Oy are supposed to provide alternate representations. Additionly %Ef implemented to represent short month name / day or- der of the date, %EF to represent long month name / day order and %OB to represent alternative months names (used standalone, without day mentioned). %e is replaced by the day of month as a decimal number (1-31); single digits are preceded by a blank. %G is replaced by a year as a decimal number with century. This year is the one that contains the greater part of the week (Monday as the first day of the week). %g is replaced by the same year as in ``%G'', but as a decimal number without century (00-99). %H is replaced by the hour (24-hour clock) as a decimal number (00-23). %h the same as %b. %I is replaced by the hour (12-hour clock) as a decimal number (01-12). %j is replaced by the day of the year as a decimal number (001-366). %k is replaced by the hour (24-hour clock) as a decimal number (0-23); single digits are preceded by a blank. %l is replaced by the hour (12-hour clock) as a decimal number (1-12); single digits are preceded by a blank. %M is replaced by the minute as a decimal number (00-59). %m is replaced by the month as a decimal number (01-12). %n is replaced by a newline. %O* the same as %E*. %p is replaced by national representation of either "ante meridiem" or "post meridiem" as appropriate. %R is equivalent to ``%H:%M''. %r is equivalent to ``%I:%M:%S %p''. %S is replaced by the second as a decimal number (00-60). %s is replaced by the number of seconds since the Epoch, UTC (see mktime(3)). %T is equivalent to ``%H:%M:%S''. %t is replaced by a tab. %U is replaced by the week number of the year (Sunday as the first day of the week) as a decimal number (00-53). %u is replaced by the weekday (Monday as the first day of the week) as a decimal number (1-7). %V is replaced by the week number of the year (Monday as the first day of the week) as a decimal number (01-53). If the week containing January 1 has four or more days in the new year, then it is week 1; otherwise it is the last week of the previous year, and the next week is week 1. %v is equivalent to ``%e-%b-%Y''. %W is replaced by the week number of the year (Monday as the first day of the week) as a decimal number (00-53). %w is replaced by the weekday (Sunday as the first day of the week) as a decimal number (0-6). %X is replaced by national representation of the time. %x is replaced by national representation of the date. %Y is replaced by the year with century as a decimal number. %y is replaced by the year without century as a decimal number (00-99). %Z is replaced by the time zone name. %z is replaced by the time zone offset from UTC; a leading plus sign stands for east of UTC, a minus sign for west of UTC, hours and minutes follow with two digits each and no delimiter between them (common form for RFC 822 date headers). %+ is replaced by national representation of the date and time (the format is similar to that produced by date(1)). %% is replaced by `%'.
#include <sys/param.h> #include <stdlib.h> char* realpath(const char *pathname, char resolvedname[MAXPATHLEN])
指定のファイルの絶対PATHを調べる。結果を第2引数に指定された 文字列に返すが、その文字列配列の大きさは MAXPATHLEN 以上でないとならない。 MAXPATHLEN は FreeBSDでは 1024 と定義されている。realpath関数の 戻り値はこの文字列のアドレスである。
realpath関数の使用例
#include <sys/param.h> #include <stdlib.h> main(){ char fname[MAXPATHLEN]; puts( realpath( "../../index.html", fname ) ); }
#include <sys/stat.h> #include <sys/types.h> int stat(const char *path, struct stat *sb) int lstat(const char *path, struct stat *sb) int fstat(int fd, struct stat *sb)
これらの関数はファイルについての様々な情報を得ます。
ファイルの指定の仕方には2通りあり、1つはファイル名を指定する方法で、
他方は現在オープンしているファイルの descriptor で指定する方法です。
stat関数とlstat関数はファイル名で指定します。fstat関数はdescriptor で指定します。
lstat関数は symbolic link のファイルについては、その symbolic link の
情報を返すのに対して、stat関数は link先のファイルの情報を返します。
ファイルについての情報はこれらの関数に与える stat型構造体変数に
格納されます。stat型の構造は以下の通り
struct stat { dev_t st_dev; /* device inode resides on */ ino_t st_ino; /* inode's number */ mode_t st_mode; /* inode protection mode */ nlink_t st_nlink; /* number or hard links to the file */ uid_t st_uid; /* user-id of owner */ gid_t st_gid; /* group-id of owner */ dev_t st_rdev; /* device type, for special file inode */ struct timespec st_atimespec; /* time of last access */ struct timespec st_mtimespec; /* time of last data modification */ struct timespec st_ctimespec; /* time of last file status change */ off_t st_size; /* file size, in bytes */ quad_t st_blocks; /* blocks allocated for file */ u_long st_blksize;/* optimal file sys I/O ops blocksize */ u_long st_flags; /* user defined flags for file */ u_long st_gen; /* file generation number */ }; #define st_atime st_atimespec.ts_sec #define st_mtime st_mtimespec.ts_sec #define st_ctime st_ctimespec.ts_sec
stat関数の使用例
struct stat st; if( stat( "file.dat", &st ) ){ perror("stat"); exit(1); } printf("st_dev = %ld\n", st.st_dev ); printf("st_ino = %ld\n", st.st_ino ); printf("st_mode = %#06o\n", st.st_mode ); printf("st_nlink = %d\n", st.st_nlink ); printf("st_uid = %ld\n", st.st_uid ); printf("st_gid = %ld\n", st.st_gid ); printf("st_rdev = %ld\n", st.st_rdev ); printf("st_atime = %ld\n", st.st_atime ); printf("st_mtime = %ld\n", st.st_mtime ); printf("st_ctime = %ld\n", st.st_ctime ); printf("st_size = %Ld\n", st.st_size ); printf("st_blocks = %Ld\n", st.st_blocks ); printf("st_blksize = %ld\n", st.st_blksize ); printf("st_flags = %ld\n", st.st_flags ); printf("st_gen = %ld\n", st.st_gen );
st_modeメンバ変数にはファイルの種類に関するさまざまな情報が集約されている。 これを調べることにより、通常ファイルかディレクトリか、端末に継ったデバイスか プロセスに継ったソケットか、実行可能ファイルか、などがわかります。 そのためのマクロが色々用意されています。
if( S_ISDIR(st.st_mode) ){ printf("It's a directory.\n"); } if( S_ISCHR(st.st_mode) ){ printf("It's a terminal.\n"); } if( S_ISREG(st.st_mode) ){ printf("It's a normal file.\n"); } if( S_ISLNK(st.st_mode) ){ printf("It's a link file.\n"); } if( S_ISFIFO(st.st_mode) ){ printf("It's a socket.\n"); } if( st.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH) ){ printf("It's executable by anyone.\n"); }
これらのマクロやdefine定数については /usr/include/sys/stat.h の 中を読むとよくわかります。
標準入力が redirect もしくは pipe されていないかがこの方法で判別できるのですが、 その目的には isatty関数を使う方が簡単です。
if( isatty(0) ){ printf("Stdin is connected to the terminal.\n"); }else{ printf("Stdin is redirected or piped.\n"); }
#include <sys/stat.h> int chmod(const char *path, mode_t mode) int fchmod(int fd, mode_t mode)
ファイルの s-bit や sticky-bit, 各 permission 等の ファイルモードを変更します。 chmod関数はファイル名で指定。fchmod関数はオープンしている descriptorで指定。 ファイルモードの指定は 8進数で行います。
fchmod,chmod関数の使用例
FILE* fptr = fopen( "file.dat", "w" ); fchmod( fileno(fptr), 0600 ); chmod( "tmp.dat", S_IRUSR|S_IRGRP|S_IROTH );
#include <stdio.h> int rename(const char *from, const char *to)
ファイルやディレクトリの名前を変えたり、移動したりします。
rename関数の使用例
rename( "oldfile", "newfile" ); rename( "oldfile", "DIR" ); rename( "OLDDIR/file", "NEWDIR" ); rename( "OLDDIR", "NEWDIR" );
#include <stdio.h> #include <unistd.h> int remove(const char *path) int unlink(const char *path)
removeもunlinkも中身は同じです。 unlinkは AT&T時代からあるので、きっとほとんどのUNIXで使用可。 removeは ANSI準拠なのでUNIXでもWindowsでもMacでも使用可。
remove関数の使用例
if( remove("file") ){ perror("remove"); exit(1); }
#include <unistd.h> int symlink(const char *name1, const char *name2) int link(const char *name1, const char *name2)
symlink関数はファイルのシンボリックリンクを作成します。 link関数はファイルのハードリンクを作成します。
symlink関数の使用例
symlink( "/usr/X11R6/bin/kterm", "mykterm" );
ちなみに、シンボリックリンクとハードリンクの違いは、 シンボリックリンクがリンク先のファイルが消されたらアクセスできなくなるのに 対して、ハードリンクがリンク先のファイルが消されてもアクセスできることです。
#include <unistd.h> int truncate(const char *path, off_t length) int ftruncate(int fd, off_t length)
ファイルのサイズを指定サイズに変更します。ファイルを空にするのに便利です。
truncate関数の使用例
truncate( "file", 0 );
#include <sys/types.h> #include <dirent.h> DIR* opendir(const char *filename) struct dirent* readdir(DIR *dirp) int closedir(DIR *dirp) struct dirent { unsigned long d_fileno; /* file number of entry */ unsigned short d_reclen; /* length of this record */ unsigned char d_type; /* file type */ unsigned char d_namlen; /* length of string in d_name */ char d_name[255 + 1]; /* name must be no longer than this */ };
ディレクトリのファイル名は opendir, readdir, closedir の一連の関数 を次のように使うことによって得られます。ディレクトリが格納している ファイルのエントリーレコードをひとつづつ読み出すのです。
opendir,readdir,closedir関数の使用例
DIR* dir = opendir("."); struct dirent* dp; while( (dp = readdir(dir)) != NULL ){ printf("%#06o %s\n", DTTOIF(dp->d_type), dp->d_name ); } closedir(dir);
dirent構造体の d_type メンバ変数をマクロ DTTOIF で加工した値は stat構造体の st_mode メンバ変数のファイル種を表す値と同じになるので S_ISDIRマクロなどでファイル種を判別することができます。
#include <unistd.h> #include <sys/stat.h> int mkdir(const char *path, mode_t mode) int rmdir(const char *path)
mkdirの modeにはディレクトリのモードを chmod関数と同じように指定します。
mkdir,rmdir関数の使用例
mkdir( "NEWDIR", 0755 ); mkdir( "NEWDIR2", S_IRWXU ); rmdir( "OLDDIR" );
#include <sys/types.h> #include <unistd.h> uid_t getuid(void) uid_t geteuid(void) int setuid(uid_t uid) int seteuid(uid_t euid)
getuidとsetuid関数はそのプロセスの実ユーザーIDを取得/設定します。 もちろんsetuidを使用できるのは super userの権限で動くプロセスのみです。 geteuidとseteuid関数は実効ユーザーIDを取得/設定します。 set-user-id bitの立ったプログラムのプロセスの実効ユーザーIDは プログラム所有者のUIDと同じですが、seteuid関数によって実行者のユーザーIDに 変更することができます。
rootのユーザーIDは 0番。nobody のユーザーIDは 65534番です。
UID関連関数の使用例
printf("UID=%d EUID=%d\n", getuid(), geteuid() ); if( seteuid( getuid() ) ){ fprintf( stderr, "Can not change EUID.\n"); exit(1); }
#include <unistd.h> #include <sys/types.h> #include <pwd.h> struct passwd{ char* pw_name; /* user name */ char* pw_passwd; /* encrypted password */ uid_t pw_uid; /* user uid */ gid_t pw_gid; /* user gid */ time_t pw_change; /* password change time */ char* pw_class; /* user access class */ char* pw_gecos; /* Honeywell login info */ char* pw_dir; /* home directory */ char* pw_shell; /* default shell */ time_t pw_expire; /* account expiration */ }; char* getlogin(void) struct passwd* getpwnam(const char *login) struct passwd* getpwuid(uid_t uid)
getlogin関数はログインする際に用いられたユーザー名を返します。 なのでsuコマンドやsetuid関数でユーザー名を変更しても元のユーザー名が返ります。 他方、getuid関数は現在のユーザーIDを返します。
getpwuid、getpwnam関数はそれぞれユーザーID、ユーザー名で そのユーザーについての情報を検索して報告します。
getpwnam,getpwuid関数の使用例
struct passwd* pw = getpwnam( getlogin() ); //または //struct passwd* pw = getpwuid( getuid() ); if( pw == NULL ){ perror("getpwnam or getpwuid"); exit(1); } printf("pw_name = %s\n", pw->pw_name ); /* ユーザー名 */ printf("pw_passwd = %s\n", pw->pw_passwd ); /* 暗号化パスワード */ printf("pw_uid = %d\n", pw->pw_uid ); /* user uid */ printf("pw_gid = %d\n", pw->pw_gid ); /* user gid */ printf("pw_change = %s", ctime(&pw->pw_change) ); /* 変更時刻 */ printf("pw_class = %s\n", pw->pw_class ); /* ユーザーの階級 */ printf("pw_gecos = %s\n", pw->pw_gecos ); /* ユーザーの名前 */ printf("pw_dir = %s\n", pw->pw_dir ); /* home directory */ printf("pw_shell = %s\n", pw->pw_shell ); /* default shell */ printf("pw_expire = %s", ctime(&pw->pw_expire) ); /* 有効期限 */ printf("pw_fields = %d\n", pw->pw_fields ); /* 内部処理用 */
#include <stdio.h> #include <stdlib.h> int system(const char *string) FILE* popen(const char *command, const char *type) int pclose(FILE *stream)
/bin/shを使ってコマンドを実行します。なので、コマンド引数、パイプ、 redirection、複数コマンド、sub-shell等の複雑なコマンドラインを指定することが 出来ます。しかし、その分、非効率的になっています。
system関数で実行されたコマンドの入出力は標準入出力です。
popen関数で実行されたコマンドの入出力はどちら一方のみを
呼び出したプロセスに繋げることが出来ます。コマンドの出力を読みたいなら
popenの第2引数に "r" を、コマンドの入力に書き込みたいなら "w" を指定します。
読み書き両用にはできません。
system, popen関数の使用例
system("(cd public_html; ls -l) | grep html > list.dat"); FILE* pipe = popen("ls -l", "r"); char buf[256]; while( fgets( buf, sizeof(buf), pipe ) ){ fputs( buf, stdout ); } pclose(pipe); FILE* pipe = popen("mail -s 'test' someone", "w"); fprintf( pipe, "Hello, Mr. Someone!\n" ); pclose(pipe);
#include <unistd.h> int execl(const char *path, const char *arg, ...) int execv(const char *path, char *const argv[]) int execlp(const char *file, const char *arg, ...) int execvp(const char *file, char *const argv[]) int execle(const char *path, const char *arg, ..., char *const envp[]) int execve( const char *path, char *const argv[], char *const envp[]) int exect(const char *path, char *const argv[], char *const envp[])
execve関数がexec関数ファミリーの親。各関数は引数を加工して
execve関数を実行する。
execlとexeclpとexecle の l は listの略でコマンド引数を可変長引数で与える。
execvとexecvpとexecve の v は vectorの略でコマンド引数を配列で与える。
execlpとexecvpの p は path の略で環境変数PATHを参照してコマンドを探してくれる。
execleとexecveのeは envirionment の略で環境変数を与える。
execファミリー関数の使用例
execl( "/bin/ls", "ls", "-l", NULL ); char* argv[] = { "ls", "-l", NULL }; execv( "/bin/ls", argv ); execlp( "ls", "ls", "-l", NULL ); char* argv[] = { "ls", "-l", NULL }; execvp( "ls", argv ); char* envv[] = { "CONTENT_LENGTH=1024", NULL }; execle( "/bin/ls", "ls", "-l", NULL, envv ); char* argv[] = { "ls", "-l", NULL }; execve( "/bin/ls", argv, envv );
exec関数ファミリーの関数はコマンドを実行できたら、制御を呼び出しもとに 返さない。もし返ってきたらそれはコマンドの実行に失敗している。
execl("/bin/ls", "ls", "-l", NULL ); perror("execl"); exit(1);
execでコマンドを実行した後もプログラムで何かしたいなら fork関数で プロセスを分岐しておく。
ちなみに、main関数でコマンド引数や環境変数を受け取るには次のようにする。
int main( int argc, char* argv[], char* envv[] ) { int i; for( i=0; argv[i]; i++ ){ puts( argv[i] ); } for( i=0; envv[i]; i++ ){ puts( envv[i] ); } return 0; }
#include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> pid_t fork(void) pid_t wait(int *status) pid_t waitpid(pid_t wpid, int *status, int options) void exit(int status) int atexit( void (*function)(void) )
fork関数は現在のプロセスのコピーを作り、それを子プロセスとして 走らせます。コピー元である親プロセスがfork実行までに確保した メモリやファイルなどすべてが子プロセスにコピーされます。
プロセスがfork関数を実行すると、2つのプロセスがそれ以降のコードを ほぼ同時に実行することになります。 2つのプロセスの違いは fork関数の戻り値だけです。 この振舞は他の関数とは大きく異なるので理解するのに少し時間がかかるでしょう。
fork関数がプロセスの分岐に失敗すると -1 を返します。 ですが失敗が起こるのは プロセスをコピーするだけのメモリが無かったり、 プロセスが既にあまりにも多く走っている場合のみなので、 失敗しないと思っていても大丈夫でしょう。もちろん用心することに越した事は 無いですが。
fork関数は親プロセスに対してはコピーした子プロセスのプロセスIDを 返します。子プロセスに対しては0を返します。なのでfork関数が返す プロセスIDの値でプロセスを区別できます。
fork関数の使用例
if( fork() ){ for( int i=0; i<4; i++ ){ printf("This is parent.\n"); sleep(1); } wait(NULL); }else{ for( int i=0; i<4; i++ ){ printf("This is child.\n"); sleep(1); } exit(0); }
ここで子プロセスの終了を待つのに wait関数を用いました。 wait関数はそのプロセスに子プロセスが1つないし複数あれば、 子プロセスの1つが終了するまで待って、 そのプロセスを片付ける処理を行い、その子プロセスIDを返します。 子プロセスが無ければ -1 を返します。 また引数に指定したアドレスに子プロセスの終了状態を表す値を格納します。 wait関数を以下のように使うことができます。
int status; pid_t wpid = wait(&status); printf("Buried child %d with exit status %d\n", wpid, WEXITSTATUS(status) );
wait関数は任意の子プロセスの終了を処理しますが、 指定のプロセスIDの子プロセスだけを処理する waitpid関数があります。
waitpid関数の第1引数に-1を指定するとwait関数同様に 任意の子プロセスの終了を処理します。戻り値は処理した子プロセスのIDです。 waitpid関数の第3引数に WNOHANG を指定すると、指定のpidの 子プロセスがまだ終了していないなら、待たないで0を返して戻ります。 子プロセスが1つも無ければ-1を返します。int status; waitpid( pid, &status, 0 );
親プロセスが子プロセスが終了するまで待ち続けて何もしないのは 時間の無駄です。子プロセスが終了すると SIGCHLD シグナルが親プロセスに 送られてくるので、そのシグナルハンドラで waitを実行すると効率良いです。 この際に気をつけることは、複数の子プロセスがほぼ同時に終了すると、 ほぼ同時にSIGCHLDシグナルが親プロセスに送られて、 1つのシグナルを処理している間に、後続のシグナルが無視されてしまいます。 なので SIGCHLDシグナルを受けたら waitpid を終了子プロセスが無くなるまで 繰り返します。
void Wait( int sig ) { while( waitpid(-1,NULL,WNOHANG) > 0 ); signal( SIGCHLD, Wait ); } main() { signal( SIGCHLD, Wait ); if( fork() ){ // parent ... }else{ // child ... exit(0); } }
子プロセスより先に親プロセスが亡くなると、その子プロセスは
initプロセスの養子プロセスとなります。
子プロセスが亡くなっても、親プロセスがwaitで埋葬するまでは
ゾンビとして漂い続け成仏しません。
initプロセスは養子プロセスを適切に埋葬します。
プログラムはexit関数によって何時どこで終了するか わからないけれど、終了時に片付けておきたい作業がある場合に atexit関数を使います。 atexit関数はプログラム終了時に自動的に呼ばれることになる 関数を登録します。最大32関数を登録できます。
atexit関数の使用例
ただし、外部からCtlr-Cなどのシグナルを受けたり 内部で segmentation faultなどを起こしたりして、強制的に 終了させられてしまう場合には、 atexit関数で登録した関数は実行されません。void func( void ) { puts("Good Bye!"); } main() { atexit(func); do_something(); }
登録した関数での作業の例として、 malloc等で確保したメモリの解放やファイルのクローズなどが あるでしょうが、実際、プログラム終了時には メモリやファイルはOSによって適切に解放されることになっていますので それらの作業を atexit関数を使って行わせる意味はありません。
C++では destructorを設定しておけば大抵の終了処理は賄えます。 destructorもシグナル受信の際には作動しません。
シグナルを受信して強制終了させられる際にも atexitで登録した関数、もしくは destructorを作動させるには 想定されるあらゆる種類のシグナルに対して exit関数を シグナルハンドラに設定しておくことです。
もちろんこれでも SIGKILL では作動しません。そもそも そこまでして終了時に作業しなければならないような 立場にプログラムを置かないことが賢明でしょう。signal( SIGTERM, exit ); signal( SIGINT, exit ); signal( SIGSEGV, exit ); signal( SIGBUS, exit );
C++では外部オブジェクトの constructor と destructor を利用して main関数よりも先に実行するコードとmain関数の後に実行するコードを 指定することができます。
こうなるともはやmain関数は盲腸の如く無用になります。 Main methodからプログラムが始まり、まるでJavaのようです。class Main{ public: Main(){ puts("Program has started."); } ~Main(){ puts("Program has ended."); } } Program; main(){}
#include <signal.h> int kill(pid_t pid, int sig) void (*signal(int sig, void (*func)(int)))(int)
シグナルはプロセス間の最も原始的な通信手段といえるでしょう。 色々な種類のシグナルがあり、それぞれの役目がだいたい決まっています。 ほとんどのシグナルがプロセスを終了させるものですが、シグナルの種類によって 終了間際の動作の仕方に多彩な変化を指示することができます。
シグナルを受信した際の動作はsignal関数に 関数ポインタを指定することで設定できます。プロセスがシグナルを 受信すると、その場の作業を中断して、シグナルハンドラの関数が実行され、 その後、元の作業を継続します。
シグナル定数 | シグナルを受けたプロセスの Defaultでの動作 |
プログラムでの使い道 |
SIGKILL | 終了する | 終了の最終手段、プログラムで応対を変更することはできない。 |
SIGHUP | 終了する | 再起動させることが多い。 |
SIGINT | 終了する | 別の作業を行わせることが多い。 |
SIGQUIT | coreを吐いて終了 | 無視させることが多い。 |
SIGTERM | 終了する | 無視、もしくは所定の作業の後に終了させることが多い。 |
SIGALRM | 終了する | タイマーを指定した場合に、タイムアウト時に送信されてくるので、 タイムアウトの作業を行わせることが多い。 |
SIGCHLD | 無視する | 子プロセスが終了した時に親プロセスに送信されてくるので、 子プロセスを埋葬する作業を行わせることが多い。 |
SIGPIPE | 終了する | 書き込めないファイルやソケットに無理に書き込ませると発生するので、 書き込みをやめさせることが多い。 |
SIGFPE | 終了する | 無理な浮動小数点演算を行わせると発生するので、 すなおに終了させることが多い。 |
SIGBUS | coreを吐いて終了 | メモリへの書き込みの異常で発生するので、 すなおに終了させることが多い。 |
SIGSEGV | coreを吐いて終了 | メモリへの書き込みの異常で発生するので、 すなおに終了させることが多い。 |
SIGSTOP | 一時停止する | プログラムで応対を変更することはできない。 |
SIGCONT | 一時停止解除 | 一時停止していたプロセスを再始動させる。 |
kill,signal関数の使用例
kill( pid, SIGTERM ); void sigint_handler( int sig ){ signal( SIGINT, SIG_IGN ); puts("Received SIGINT"); signal( SIGINT, sigint_handler ); } signal( SIGINT, sigint_handler );
登録したシグナルハンドラは、それを実行するたびに登録解消されてしまいます。 なのでシグナルハンドラを実行中に再びシグナルが来るとプロセスが終了して しまいます。それを防ぐためにシグナルハンドラ冒頭でそのシグナルを無視する ようにします。またシグナルハンドラ末尾で再度シグナルハンドラを設定します。 このような対応のため、複数のシグナルがほぼ同時に受信された場合には いくつかのシグナルが無視されることになります。なのでシグナルは 確固たるプロセス間の通信にはなりません。
システムコール関数のうちいくつくは その実行中にシグナルを受信するとシステムコールの作業を辞めてエラーを 返すものもあります。
#include <setjmp.h> #include <unistd.h> u_int alarm(u_int seconds) u_int ualarm(u_int microseconds, u_int interval) int setjmp(jmp_buf env) void longjmp(jmp_buf env, int val)
入力や回線の接続などで、指定時間以内に期待の動作が動かなければ タイマを発動して別の動作を行わせるにはアラームと非局所ジャンプを 使うのが一般的です。
alarm関数は指定秒数後にこのプロセスに SIGALRM シグナルを1回だけ 送信するように設定します。ualarm関数は指定micro秒後から、指定間隔で SIGALRM シグナルを送信するように設定します。このようなアラームの 設定を解除するには alarm関数 0 を設定します。
setjmp, longjmp関数の動作原理は難しいですが、
要するに setjmp関数を呼んだ位置が longjmp関数のジャンプ先に設定されます。
より詳しくは、setjmp関数は呼び出し元のプログラムでの位置(コードアドレス)を
与えられた jmp_buf型の変数に記録し、呼び出し元に 0を返します。
longjmp関数はあたかも先のコードアドレスからこの関数が呼ばれたかのように
そのアドレスにプログラムの流れ戻し、その際、指定した
値を戻り値として返します。
longjmp,setjmp関数の使用例
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <setjmp.h> jmp_buf stack; void timeout( int ){ longjmp( stack, 1 ); } main() { int n; if( setjmp(stack) == 0 ){ printf("Input a number within 2 seconds : "); signal( SIGALRM, timeout ); alarm(2); scanf("%d", &n ); alarm(0); }else{ puts("Timeout!"); } }
入力のタイムアウトにはこの方法以外にも select関数を用いる方法もあります。
#include <sys/types.h> #include <sys/time.h> #include <unistd.h> #include <stdio.h> #include <string.h> int select( int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout )
select関数は複数のファイル descriptor とソケットが入出力の準備 ができているかを検査します。 よくある利用例として、複数のソケットのうちどれか1つに データが到着するまで待つのに使われます。select関数自身にタイムアウト を指定することができるので、指定時間内に入力が無ければタイムアウト にすることができます。また、そのタイムアウトの指定には micro秒で 指定することから、micro秒単位の sleep関数としても使うことができます。
検査対象のファイル descriptor とソケットの番号は fd_set型の構造体の変数で指定します。この変数への読み書きは専用のマクロを 用いて行います。
select関数の第2,3,4引数にはfd_set型変数のアドレスを渡すことで、それぞれ 読み込み、書き込み、例外の検査対象を指定します。 そして検査結果がそのアドレスに上書きされます。 ここに NULL を指定すればその項目については検査されません。
select関数の第1引数 nfds には select関数が検査する descriptorの 個数を指定します。ただし select関数は 0番から nfds-1番までの nfds個 の descriptor を検査するので、nfdsには検査してほしい番号の最大値に1足した 値を指定します。select関数はこの範囲のうち、第2,3,4引数で指定した番号を 検査します。
第5引数にはタイムアウトの時間を timeval構造体の変数のアドレスで指定します。 これをNULLにすれば永久に待ちます。
select関数は該当する descriptorの個数を返します。 エラーなら -1、タイムアウトなら 0 を返します。
以下の例は複数のソケットの番号を格納した配列 s[N] のうち、 データが届いているソケットの番号を返す関数の作成例です。
select関数の使用例
#include <sys/types.h> #include <sys/time.h> #include <unistd.h> #include <stdio.h> #include <string.h> #define N 4 //---- ソケット配列 s[] のうち、データが届いているソケットを報告する関数 int Readable( int s[N] ) { int i; fd_set fdset; // fd_set変数を0に初期化する FD_ZERO( &fdset ); // 各ソケットの番号を登録する for( i=0; i<N; i++ ){ FD_SET( s[i], &fdset ); } // ソケット番号の最大値を探す int max_s=0; for( i=0; i<N; i++ ){ if( max_s < s[i] ) max_s = s[i]; } // タイムアウトを10秒に設定 struct timeval tv; tv.tv_sec = 10; tv.tv_usec = 0; // ソケットにデータが届くまで待つ int n = select( max_s+1, &fdset, NULL, NULL, &tv ); if( n == -1 ){ // 何らかのエラー perror("select"); return 0; }else if( n == 0 ){ // タイムアウト fprintf( stderr, "select timeout\n" ); return 0; }else if( n>0 ){ // どれかにデータが届いた // どれが読み込み可能か検査 for( i=0; i<N; i++ ){ if( FD_ISSET( s[i], &fdset ) ){ return s[i]; // 読み込めるソケットを返す } } return 0; } return 0; }
標準入力から読み込めるかどうかの検査は以下のように簡略化 することもできます。少々無理矢理な方法ですが。
struct timeval tv = {2,0}; char buf[256]; int test = 1; if( select( 1, (fd_set*) &test, NULL, NULL, &tv ) > 0 && test ){ fgets( buf, sizeof(buf), stdin ); }else{ printf("error or timeout\n"); }
select関数 usleep関数の代わりに使うこともできます。 usleep関数がBSD系のみであるのに対して、select関数は全UNIXで使えます。
struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 10000; // sleep 10 mili-second select( 0, NULL, NULL, NULL, &tv );
#include <unistd.h> int pipe(int *fildes)
pipe関数の引数には2成分のint型配列のアドレスを与えます。
すると、次のような読み書きの通信ができます。int fds[2]; pipe(fds);
つまり、同じプロセス間を結ぶパイプラインで、その読み込み口が fds[0] で書き込み口が fds[1] です。しかしこれだけでは全然意味がありません。 意味が生じるのは forkでプロセスを分岐してからです。 forkでプロセスをコピーすると、ソケットやdescriptorは共有されるので 親プロセスの fds[1] への書き込むも、個プロセスの fds[1] への 書き込みも、同じパイプラインを通って、親プロセスの fds[0] からも読み込めますし 子プロセスの fds[0] からも読み込めます。なので親子間の通信に利用できます。 しかしこの通信は対話的な双方向の通信には向いていません。なぜなら 親がデータをパイプに書き込んで、子が読む前に、親がパイプを読み出す操作をしたら、 データは親が読んでしまって、子は読めません。pipe関数によるパイプラインは 一方的なデータ通信にしか向いていません。双方向の通信のための ソケットペアは socketpair関数によって作成します。char buf[256]; strcpy( buf, "Hello"); write( fds[1], buf, strlen(buf)+1 ); read( fds[0], buf, sizeof(buf) ); printf("Received [%s]\n", buf );
以下の例は親プロセスから子プロセスにメッセージを送ります。 親プロセス側にはパイプラインの読み込み端は不要なのでfds[0]を閉じます。 子プロセス側にはパイプラインの書き込み端は不要なのでfds[1]を閉じます。 もちろん通信が終ったら他方も閉じます。
pipe関数の使用例
char buf[256]; int fds[2]; pipe(fds); if( fork() ){ close(fds[0]); strcpy( buf, "Hello" ); write( fds[1], buf, strlen(buf)+1 ); printf("Parent send: %s\n", buf ); close(fds[1]); }else{ close(fds[1]); read( fds[0], buf, sizeof(buf) ); printf("Child received: %s\n", buf ); close(fds[0]); }
#include <unistd.h> int dup(int oldd) int dup2(int oldd, int newd)
dup,dup2関数は指定の descriptorの性質を他の descriptor に上書きコピーします。 例えば書き込む用ファイル descriptor の変数 fd に対して、
とすれば、fdの値の descriptor の性質が 1番 descriptor に上書きコピーされるので、 1番 descriptor が書き込む用ファイル descriptor になります。 つまり標準出力に送られたデータはファイルに書き込まれます。 この時、fd変数の値の descriptor に書き込んでも依然ファイルに書き込まれますが もはや不要であるうえに2系統書き込みは混乱の元なので close(fd)として閉じたほうが良いでしょう。dup2( fd, 1 );
dup2関数は close関数と dup関数を併せたものと考えて良いでしょう。
close(1); dup(fd);
dup2関数は以下のように、UNIXコマンドの入出力を プログラムとやりとりすることによく用いられます。
dup2関数の使用例
コマンドが高水準の標準出力に書き込む場合には データがバッファされてから送信されるので、受信するに時間がかかる ことに注意してください。int fds[2]; pipe(fds); if( fork() ){ close(fds[1]); FILE* pptr = fdopen( fds[0], "r" ); char buf[256]; while( fgets( buf, sizeof(buf), pptr ) ){ fputs( buf, stdout ); } fclose(pptr); close(fds[0]); }else{ close(fds[0]); dup2(fds[1],1); close(fds[1]); execl("/bin/ls", "ls", "-l", NULL ); perror("execl"); exit(1); }
#include <sys/types.h> #include <sys/socket.h> int socketpair(int d, int type, int protocol, int *sv)
socketpair関数はプロセス内を結ぶ全二重の通信回線と その両端のソケットを作成します。引数が4つありますが 以下のようにしか使用できません。
これにより互いに継った2つのソケット sv[0], sv[1] が作成されます。 このソケットはもちろん読み書き両用です。 socketpair関数はどういうわけか ENOENT(No such file or directory)の エラーを返しますが、それは無視して構わなさそうです。int sv[2]; socketpair( AF_LOCAL, SOCK_STREAM, 0, sv );
socketpair関数の使用例
この読み書き共用のソケットに標準出力と標準入力をdupさせるには 次のようにします。char buf[256]; int sv[2]; if( socketpair( AF_LOCAL, SOCK_STREAM, 0, sv ) && errno != ENOENT ){ perror("socketpair"); exit(1); } if( fork() ){ close(sv[1]); strcpy( buf, "Hello! How are you?" ); write( sv[0], buf, strlen(buf)+1 ); read( sv[0], buf, sizeof(buf) ); printf("Parent received : %s\n", buf ); close(sv[0]); }else{ close(sv[0]); read( sv[1], buf, sizeof(buf) ); printf("Child received : %s\n", buf ); strcpy( buf, "Fine, thank you." ); write( sv[1], buf, strlen(buf)+1 ); close(sv[1]); }
高水準で標準出力に書き込む場合は、データがバッファされるので 対話的に双方向で通信する際にデットロックなどの支障がでるかもしれません。 高水準標準出力のバッファははずしたほうが良いでしょう。dup2(sv[1],0); dup2(0,1); close(sv[1]);
setbuf( stdout, NULL );
サーバーの例
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <errno.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> void Wait( int sig ) { while( waitpid(-1,NULL,WNOHANG) > 0 ); signal( SIGCHLD, Wait ); } int main( void ) { int bs, s; struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons(1234); if( (bs = socket( AF_INET, SOCK_STREAM, 0 )) < 0 ){ perror("socket"); exit(1); } if( bind( bs, (sockaddr*)&saddr, sizeof(saddr)) < 0 ){ perror("bind"); exit(1); } if( listen( bs, 5 ) < 0 ){ perror("listen"); exit(1); } signal( SIGCHLD, Wait ); while(1){ struct sockaddr_in caddr; int len; if( (s=accept( bs, (sockaddr*)&caddr, &len )) < 0 ){ if( errno == EINTR ) continue; perror("connect"); exit(1); } fprintf( stderr, "Connected from %s %d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port) ); if( fork() ){ close(s); continue; } close(bs); FILE* pptr = fdopen( s, "r+" ); char buf[256]; while( fgets( buf, sizeof(buf), pptr ) ){ if( buf[0] == '\r' ) break; fputs( buf, pptr ); } close(s); exit(0); } close(bs); }
クライアントの例
#include <stdio.h> #include <string.h> #include <unistd.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> int main( void ) { char hostname[256] = "localhost"; // get the IP address of the web server. struct hostent* hp; if( (hp = gethostbyname( hostname )) == NULL ){ fprintf( stderr, "gethostbyname\n"); exit(1); } // get the address of the internet domain socket to the HTTPD. struct sockaddr_in sadd; sadd.sin_family = AF_INET; sadd.sin_port = htons(1234); sadd.sin_addr = *(struct in_addr*) hp->h_addr; // create a new socket. int s = socket( AF_INET, SOCK_STREAM, 0 ); // connect the socket to the httpd. if( connect( s, (sockaddr*)&sadd, sizeof(sockaddr_in) ) < 0 ){ perror("connect"); exit(1); } FILE* pptr = fdopen( s, "r+" ); char buf[256]; while( fgets( buf, sizeof(buf), stdin ) ){ fputs( buf, pptr ); fgets( buf, sizeof(buf), pptr ); fputs( buf, stdout ); } fputs( "\r", pptr ); close(s); return 0; }