C言語関数の復習

最後に、C言語のさまざまな関数をおさらいしておきましょう。



● 入出力


  • printf,fprintf,sprintf,snprintf,vprintf,vfprintf,vsprintf,vsnprintf
    書式制御付き文字列変換

    #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値の出力の色々

    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]
    
    double値の出力の色々
    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*値の出力の色々
    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_shortu_int
    float double
    この暗黙の型変換があるため、float値でも%fで表示できます。 char,shortで%d、u_char,u_shortで%uも同じ理由。

    signed, unsignedでの混乱の例

    char c = 255;
    
    printf("%d %x %u\n", c, c, c );
    
    // -1 ffffffff 4294967295
    
    数値255の実体は 0xff、これを符合付の char で解釈すると -1 になります。 なのでchar型変数 c は -1 となる。printfに渡される際に int型に暗黙にキャスト変換されるのでこれはint型の-1となります。 int型の-1の実体は 0xffffffff です。 これを %dで表示すると -1 となり、%x で表示すると ffffffff、%uで表示すると 4294967295 となり、最初に指定した値とはまるで 違う値が表示されるように見えます。
    char型変数にその範囲外の値を代入するのが間違いの元ですが、 バイナリデータは0x80以上の値になるので、それを表示しようとすると この現象が起きます。これを防ぐにはprintfでu_charに明示的に キャスト変換するのです。
    char c = 255;
    
    printf("%d %x %u\n", (u_char)c, (u_char)c, (u_char)c );
    
    // 255 ff 255
    
    暗黙の型変換の規則により u_char の値は u_int に変換されるので、 char型の 0xffの値は u_int 型の 0x000000ff に変換され、期待通りに表示されます。 %dと%uで同じ表示になったのはこの値の実体が int でも u_int でも同じだから で、もっと巨大な値だったら異なります。

    fprintfの使用例

    fprintf( fptr, "hello, world");
    fprintf( stderr, "hello, world");
    fprintf( stdout, "hello, world");
    
    stderrに出力するとバッファリングされずに直ちに表示されます。 stdoutに出力するとバッファリングされて遅れて表示されます。 直ちに表示させるには fflush(stdout)を唱えます。

    sprintfの使用例

    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 );
    
    sprintfは strcpyや strcat より遥かに便利でしかも文字数が返ってきます。 ただしバッファ溢れに注意、最悪の場合にはセキュリュティーホールにもなります。

    snprintfの使用例

    char str[32], strA[32], strB[32];
    
    int len = snprintf( str, sizeof(str), "%s%s", strA, strB );
    
    バッファ溢れの心配がありません。snprintfを備えていないOSもあります。 sizeof演算子を配列名に演算すれば配列のサイズになりますが、 ポインタに演算したらポインタ変数の大きさ 4 になることに注意。

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

  • scanf,fscanf,sscanf,vscanf,vsscanf,vfscanf
    書式制御付き入力文字列変換

    #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 );
    
    文字、複数文字、文字列の読み込み
    char c;
    char cs[8];
    char str[16];
    
    scanf("%c %8c %s", &c, cs, str );
    
    %8cは空白も含めて8文字読み込み、NULLを付加しない。 %sは空白があるまで読み込みNULLを付加します。

    読み込みの制限、ガイド

    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つづつ読み込み続けるには

    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 );
    }
    
    これは面倒なので fgets と sscanfを使う方が楽です。

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


    ● 文字列操作

  • strcpy,strncpy
    文字列のコピー

  • strcat,strncat
    文字列の連結

  • strcmp,strncmp
    文字列の比較

  • strchr,strrchr
    文字列中の指定1文字の在処の検索

  • strpbrk
    文字列中の指定複数文字の在処の検索

  • strspn,strcspn
    文字列中の指定複数文字の長さの調査

  • strstr
    文字列中の指定文字列の検索

  • strtok,strsep
    文字列の分割

  • strlen
    文字列の長さの調査

  • strerror
    システムコールエラー文字列の入手

    #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の使用例。

    char str1[32], str2[32], str3[32];
    
    strcpy( str1, "hello," );
    strcpy( str2, "world!" );
    strcpy( str3, str1 );
    strcat( str3, str2 );
    
    strcpy,strcatを使うよりsprintf関数を使う方が簡潔であることが多いです。
    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の使用例。

    char str[] = "sin,cos,tan,sqrt,,log";
    char *ptr;
    
    for( ptr=strtok( str, "," ); ptr; ptr=strtok( NULL, "," ) ){
      printf("[%s]\n", ptr );
    }
    
    strtokには1回目の呼出の時だけアドレスを渡して、以降はNULLを渡します。 strtokは受け取ったアドレスに書き込むを行うので、文字列は書き込み可能な メモリ上になければなりません。strtokは空の文節を返せません。

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

  • getopt
    コマンドオプションの精製

    #include <stdlib.h>
    
    int getopt( int argc, char** argv, const char* list )
    

    getopt関数は main関数が受け取るコマンドオプションを 解析するのに大変便利な関数です。 例えば、以下のようなコマンドオプションが与えられるとします。

    a.out -a -b -c file.dat -def
    
    これらの引数をオプションとして処理するには以下のようにします。

    getopt関数の使用例

    #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関数の第3引数に受け付け可能なオプション文字列のリストを 指定します。コロン':'が後続した文字のオプションには例のように オプション引数が付くことになります。
    getopt関数は与えられたコマンド引数からオプションとなるものをひとつづつ 報告します。 オプション引数があればその文字列へのポインタが広域変数 optarg に 格納されてきます。
    リストにないオプションが指定さたり、オプション引数が必要なのに 指定されていないと、getopt関数は stderrに文句を出します。 この文句を邪魔と感じるなら、冒頭で以下のように設定することで この文句を抑止できます。
    extern int opterr;
    opterr=0;
    
    コマンド引数を全部調べ終えるか、 ダッシュ'-'で始まらない項目があると getopt関数は EOF を返します。 残ったオプション以外のコマンド引数は自分で解析します。 以下のように調整すると、残ったコマンド引数を探しやすくなります。
    extern int optind;
    argc -= optind;
    argv += optind;
    
    同じプログラム中で、別なポインタ配列をコマンド引数と同様に getopt関数を使って解析したいなら、その使用前に以下の初期化作業を 行っておかなければなりません。
    extern int optreset;
    extern int optind;
    optreset = 1;
    optind   = 1;
    

  • regcomp, regexec, regerror, regfree
    正規表現での文字列の検索

    #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関数にはその変数のアドレスを指定します。

    regex_t preg;
    regcomp( &preg, "abc[0-9]", REG_NEWLINE|REG_EXTENDED );
    
    以後この変数 preg を記述子として正規表現を指定します。 第3引数には正規表現の解釈を微妙に変更させるパラメタを指定します。 上記の指定で拡張された正規表現を扱うようになり、 さらに行末を認識できるようになります。ここで 使用できるパラメタは以下の通りです。

  • REG_BASIC
    この値は実は0。拡張されていない正規表現の文法で処理します。
  • REG_EXTENDED
    拡張された正規表現の文法で処理するようになります。
  • REG_ICASE
    大文字小文字を区別しなくなります。
  • REG_NOSUB
    検索結果でどの文字列にマッチしたかを報告しなくなります。
  • REG_NEWLINE
    改行文字を行末として認識するようになります。
  • regexec関数は与えられた文章中に正規表現にマッチする 文字列がないかを検査します。文章の先頭から検索して1つを見つけると、 その文字列の先頭と末尾の直後の文章中での位置を返します。

    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 );
    }
    
    検索結果は第4引数に指定した regmatch_t 型の配列変数に 格納されますが、普通の使用ではその第0要素だけで十分です。 そのメンバ変数 rm_so, rm_eo にマッチした文字列の 先頭と末尾の直後の文章中での位置が格納されています。ただし この型は long long型のため intにキャスト変換しないと扱えません。

    同じ文章の後続をさらに検索するには再び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種があります。

  • 0
    通常これを指定します。regexec関数に与えた文字列が 文頭文末を含む1文であることを想定しています。
  • REG_NOTBOL
    regexec関数に与えた文字列が、実は長い1文の中間の一部で、 文字列の先頭は文頭ではない場合に、これを指定して、 メタ文字 ^ にマッチしないようにします。
  • REG_NOTEOL
    regexec関数に与えた文字列が、実は長い1文の中間の一部で、 文字列の終端は文末ではない場合に、これを指定して、 メタ文字 $ にマッチしないようにします。
  • そもそもegrepの正規表現を知らない人が多いでしょう。 これはUNIXのシェルやDOSのcommand.comが解釈するワイルドカード とは全く異なるものです。egrepの正規表現、すなわち、 「拡張された正規表現」の文法をまずしっかり学んでおきましょう。

    ここで簡単に拡張された正規表現について解説します。 詳しくは egrep のマニュアルを参照してください。

  • \
    メタ文字 m に対して \m として メタ文字の意味を消して普通の文字とする。
    例えば正規表現 C\+\+ は 文字列 C/C++ の C++ の部分にマッチする。
  • ^
    行頭を表します。
    例えば正規表現 ^abc は行頭に abcdef となる文字列の abc の部分にマッチする。
  • $
    行末を表します。
    例えば正規表現 def$ は行末に abcdef となる文字列の def の部分にマッチする。
  • .
    任意の1文字を表します。
    例えば正規表現 a.c は文字列 abc や adc にマッチするが、acd にはマッチしない。
  • [,]
    複数の文字のうちのいずれか1文字を表します。
    例えば正規表現 a[012]b は文字列 a1b にマッチするが、ab1にはマッチしない。
  • -
    範囲内の1文字を表します。
    例えば正規表現 a[0-9]b は文字列 a1b にマッチするが、azbにはマッチしない。
  • ^
    []内で別の意味になる。範囲外の1文字を表します。
    例えば正規表現 a[^0-9]b は文字列 azb にマッチするが、a1bにはマッチしない。
  • *
    正規表現 r に対して r* として rの0回以上の繰り返しを表します。
    例えば正規表現 ab*z は文字列 az, abz, abbz にマッチする。
    正規表現 a.*z は文字列 az, abz, abcz, abzcz にマッチする。
  • +
    正規表現 r に対して r+ として rの1回以上の繰り返しを表します。
    例えば正規表現 ab+z は文字列 abz, abbz にマッチするが azにはマッチしない。
    正規表現 a.+z は文字列 abz, abcz, abzcz にマッチするが azにはマッチしない。
  • ?
    正規表現 r に対して r? として rの1回以下の繰り返しを表します。
    例えば正規表現 ab?z は文字列 az, abz にマッチするが abbzにはマッチしない。
    正規表現 a.?z は文字列 az, abz, acz にマッチするが abczにはマッチしない。
  • |
    正規表現 r1, r2 に対して r1|r2 として、両方を表します。
    例えば正規表現 abc|def は文字列 abcdef の abc と def の両方にマッチする。
    正規表現 a.c|a.d は文字列 abcaed の abc と aed の両方にマッチする。
  • (,)
    正規表現に部分構造を持たせる。正規表現 r1,r2 に対して a(r1|r2)b など。
    例えば正規表現 a([b-y]|[B-Y])z は文字列 abcdz と aBCDz の両方にマッチし、 部分構造はそれぞれ bcd と BCD にマッチしている。
  • さらに、0-9, a-zなどを表す以下の表現もあります。

    [:alnum:], [:alpha:], [:cntrl:], [:digit:], [:graph:], [:lower:], [:print:], [:punct:], [:space:], [:upper:], [:xdigit:]
    これらの範囲は ctype.h の is?関数ファミリーと同じ。 正規表現でこれらを使うには例えば [[:alnum:]]、[^[:alnum:]] とします。

    正規表現の文法を学ぶには以下のプログラムを利用すると良いでしょう。

    // 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 を以下のようにして使います。
    regex 'abc' < file
    
    ファイルの各行の正規表現にマッチする部分に +*** のマークが付きます。

    CShellのファイルに対するパターン( *, ? など) は 正規表現とは異なります。ですが若干の修正でファイル名の パターン置換を正規表現で行えるようになります。

    パターン正規表現
    ? .
    * .*
    [a-z] [a-z]
    {str1,str2} (str1|str2)
    ~naoki 該当なし
    文頭文末を表す正規表現^,$を付加することを忘れずに。

    パターンを正規表現に変換するルーチンは以下のようになるでしょう。 ただし ~の処理は省略しました。

    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;
    }
    
    ~の展開には getpwnam(), getlogin() 関数などを使いますが ファイルのマッチを検査する場合に ~が登場することは考えなくても 良いでしょう。


  • fopen, fdopen, freopen, fclose
    ファイルのオープンとクローズ

    #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+" 読み書き両用 終端 有ればそのままにする
    MS-Windows上では mode 文字列にはさらに 't' または 'b' を 加えることでテキストファイルとして読み書きするか、 バイナリファイルとして読み書きするかの選択ができます。 UNIX上ではその意味はありません。

    fdopen関数は既に低水準でファイルが開いている場合に、 それを高水準でも扱えるように、その descriptor を扱う file stream を 作成します。

    fileno関数は file stream の核をなす file descriptor の番号を返します。


  • fputs,puts,fgets,gets
    一行読み書き

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


  • fread,fwrite
    バイナリ読み書き

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

  • fseek,rewind,ftell,fflush
    ファイルポジションの変更など

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


  • feof,ferror
    ファイル終端到達、エラーの検知

    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.");
    }
    


  • tmpfile,tmpnam,tempnam
    テンポラルファイルの作成

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


  • open,close
    低水準でのファイルのオープンとクローズ

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


  • flock, lockf
    ファイルロック
    #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 );
    


  • read,write 低水準ファイル読み書き

    #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。


  • getch, kbhit
    キーの読み込み、検出

    MS-DOSには getch, kbhit 関数があり、 押されたキーの文字を即座にプログラムで処理できたり、 キーが押されているかを検査できたりしました。 しかし、これらの関数は UNIX にはありません。 しかし UNIXは入出力の詳細を多岐に渡って細かく制御でき、 getch, kbhit と同等のことを自分で作成することができます。

    UNIXでの getch関数の作り方。
    標準入力を noncanonical input mode に変更し、入力を1文字だけ待つようにする。 さらに入力文字が表示されないようにする。

    #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();
    }
    
    このgetch関数の使用前に begin_getch関数で標準入力の設定を変更し、 使用後には end_getch関数で設定を元に戻します。

    UNIXでの kbhit関数の作り方

    標準入力を noncanonical input mode に変更し、入力を待たないようにする。 さらに入力文字が表示されないようにする。
    #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();
    }
    
    このkbhit関数の使用前に begin_kbhit関数で標準入力の設定を変更し、 使用後には end_kbhit関数で設定を元に戻します。


    ● 時刻

  • time
    時刻入手

  • ctime
    時刻→文字列 変換

  • localtime, gmtime
    時刻→時刻構造体 変換

  • asctime
    時刻構造体→文字列 変換

  • mktime
    時刻構造体→時刻 変換

  • difftime
    時刻差計算

  • strftime
    時刻構造体→書式制御付文字列 変換

  • gettimeofday
    マイクロ秒単位での時刻の測定

    #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 `%'.
    

    ● ファイル操作

  • realpath
    ファイルの絶対PATHの入手

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

  • stat, lstat, fstat
    ファイルの状態の入手

    #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");
    }
    

  • chmod, fchmod
    ファイルモードの変更

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

  • rename
    ファイル名の変更、ファイルの移動

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

  • remove, unlink
    ファイルの削除

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

  • symlink, link
    リンクファイルの作成

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

    ちなみに、シンボリックリンクとハードリンクの違いは、 シンボリックリンクがリンク先のファイルが消されたらアクセスできなくなるのに 対して、ハードリンクがリンク先のファイルが消されてもアクセスできることです。


  • truncate, ftruncate
    ファイルサイズの変更

    #include <unistd.h>
    
    int truncate(const char *path, off_t length)
    int ftruncate(int fd, off_t length)
    

    ファイルのサイズを指定サイズに変更します。ファイルを空にするのに便利です。

    truncate関数の使用例

    truncate( "file", 0 );
    


    ● ディレクトリ操作

  • opendir, readdir, closedir
    ディレクトリのファイル一覧の入手

    #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マクロなどでファイル種を判別することができます。


  • mkdir, rmdir
    ディレクトリの作成と削除

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

    ● ユーザー情報


  • getuid,geteuid,setuid,seteuid
    ユーザーIDの取得、設定

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

  • getlogin, getpwuid, getpwnam
    ログイン名の取得、アカウントの分析

    #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 );    /* 内部処理用 */
    

    ● プロセス制御

  • system, popen
    プログラムのshによる実行

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

  • execl,execlp,execle,exect,execv,execvp
    プログラムの直接実行; execファミリー

    #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;
    }
    

  • fork,wait,waitpid,exit,atexit
    プロセスの分岐

    #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関数があります。

    int status;
    waitpid( pid, &status, 0 );
    
    waitpid関数の第1引数に-1を指定するとwait関数同様に 任意の子プロセスの終了を処理します。戻り値は処理した子プロセスのIDです。 waitpid関数の第3引数に WNOHANG を指定すると、指定のpidの 子プロセスがまだ終了していないなら、待たないで0を返して戻ります。 子プロセスが1つも無ければ-1を返します。

    親プロセスが子プロセスが終了するまで待ち続けて何もしないのは 時間の無駄です。子プロセスが終了すると 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関数の使用例

    void func( void )
    {
       puts("Good Bye!");
    }
    
    main()
    {
      atexit(func);
    
      do_something();
    }
    
    ただし、外部からCtlr-Cなどのシグナルを受けたり 内部で segmentation faultなどを起こしたりして、強制的に 終了させられてしまう場合には、 atexit関数で登録した関数は実行されません。

    登録した関数での作業の例として、 malloc等で確保したメモリの解放やファイルのクローズなどが あるでしょうが、実際、プログラム終了時には メモリやファイルはOSによって適切に解放されることになっていますので それらの作業を atexit関数を使って行わせる意味はありません。

    C++では destructorを設定しておけば大抵の終了処理は賄えます。 destructorもシグナル受信の際には作動しません。

    シグナルを受信して強制終了させられる際にも atexitで登録した関数、もしくは destructorを作動させるには 想定されるあらゆる種類のシグナルに対して exit関数を シグナルハンドラに設定しておくことです。

    signal( SIGTERM, exit );
    signal( SIGINT, exit );
    signal( SIGSEGV, exit );
    signal( SIGBUS, exit );
    
    もちろんこれでも SIGKILL では作動しません。そもそも そこまでして終了時に作業しなければならないような 立場にプログラムを置かないことが賢明でしょう。

    C++では外部オブジェクトの constructor と destructor を利用して main関数よりも先に実行するコードとmain関数の後に実行するコードを 指定することができます。

    class Main{
    public:
      Main(){
        puts("Program has started.");
      }
      ~Main(){
        puts("Program has ended.");
      }
    } Program;
    
    main(){}
    
    こうなるともはやmain関数は盲腸の如く無用になります。 Main methodからプログラムが始まり、まるでJavaのようです。


    ● シグナル処理

  • kill, signal
    シグナルの送信と受信

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

    登録したシグナルハンドラは、それを実行するたびに登録解消されてしまいます。 なのでシグナルハンドラを実行中に再びシグナルが来るとプロセスが終了して しまいます。それを防ぐためにシグナルハンドラ冒頭でそのシグナルを無視する ようにします。またシグナルハンドラ末尾で再度シグナルハンドラを設定します。 このような対応のため、複数のシグナルがほぼ同時に受信された場合には いくつかのシグナルが無視されることになります。なのでシグナルは 確固たるプロセス間の通信にはなりません。

    システムコール関数のうちいくつくは その実行中にシグナルを受信するとシステムコールの作業を辞めてエラーを 返すものもあります。


    ● タイムアウト

  • setjmp,longjmp,alarm,ualarm
    アラームと非局所ジャンプ

    #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関数を用いる方法もあります。


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

    ● プロセス間通信

  • pipe
    親子プロセス間の一方的な通信回線の作成

    #include <unistd.h>
    
    int pipe(int *fildes)
    

    pipe関数の引数には2成分のint型配列のアドレスを与えます。

    int fds[2];
    pipe(fds);
    
    すると、次のような読み書きの通信ができます。
    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] です。しかしこれだけでは全然意味がありません。 意味が生じるのは forkでプロセスを分岐してからです。 forkでプロセスをコピーすると、ソケットやdescriptorは共有されるので 親プロセスの fds[1] への書き込むも、個プロセスの fds[1] への 書き込みも、同じパイプラインを通って、親プロセスの fds[0] からも読み込めますし 子プロセスの fds[0] からも読み込めます。なので親子間の通信に利用できます。 しかしこの通信は対話的な双方向の通信には向いていません。なぜなら 親がデータをパイプに書き込んで、子が読む前に、親がパイプを読み出す操作をしたら、 データは親が読んでしまって、子は読めません。pipe関数によるパイプラインは 一方的なデータ通信にしか向いていません。双方向の通信のための ソケットペアは socketpair関数によって作成します。

    以下の例は親プロセスから子プロセスにメッセージを送ります。 親プロセス側にはパイプラインの読み込み端は不要なので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]);
    }
    

  • dup,dup2
    ファイル/ソケット descriptorのコピー

    #include <unistd.h>
    
    int dup(int oldd)
    int dup2(int oldd, int newd)
    

    dup,dup2関数は指定の descriptorの性質を他の descriptor に上書きコピーします。 例えば書き込む用ファイル descriptor の変数 fd に対して、

    dup2( fd, 1 );
    
    とすれば、fdの値の descriptor の性質が 1番 descriptor に上書きコピーされるので、 1番 descriptor が書き込む用ファイル descriptor になります。 つまり標準出力に送られたデータはファイルに書き込まれます。 この時、fd変数の値の descriptor に書き込んでも依然ファイルに書き込まれますが もはや不要であるうえに2系統書き込みは混乱の元なので close(fd)として閉じたほうが良いでしょう。

    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);
    }
    
    コマンドが高水準の標準出力に書き込む場合には データがバッファされてから送信されるので、受信するに時間がかかる ことに注意してください。


  • socketpair
    親子プロセス間の双方向の通信回線の作成

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int socketpair(int d, int type, int protocol, int *sv)
    

    socketpair関数はプロセス内を結ぶ全二重の通信回線と その両端のソケットを作成します。引数が4つありますが 以下のようにしか使用できません。

    int sv[2];
    socketpair( AF_LOCAL, SOCK_STREAM, 0, sv );
    
    これにより互いに継った2つのソケット sv[0], sv[1] が作成されます。 このソケットはもちろん読み書き両用です。 socketpair関数はどういうわけか ENOENT(No such file or directory)の エラーを返しますが、それは無視して構わなさそうです。

    socketpair関数の使用例

    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]);
    }
    
    この読み書き共用のソケットに標準出力と標準入力をdupさせるには 次のようにします。
    dup2(sv[1],0);
    dup2(0,1);
    close(sv[1]);
    
    高水準で標準出力に書き込む場合は、データがバッファされるので 対話的に双方向で通信する際にデットロックなどの支障がでるかもしれません。 高水準標準出力のバッファははずしたほうが良いでしょう。
    setbuf( stdout, NULL );
    

    ● ネットワーク通信

  • socket,accept,bind,connect,listen
    ネットワークソケットの作成、接続など

    サーバーの例

    #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;
    }
    

    おしまい。
    目次

    Copyright(C) by Naoki Watanabe. Oct 21st, 1995.
    渡辺尚貴 naoki@cms.phys.s.u-tokyo.ac.jp