サーバーサイドエンジニアの技術メモあれこれ

調べたことのメモがき。その他雑記もちょろちょろと。ゆるくやってます。

C言語メモ

What is C

www.c-lang.org

メモ

#includeの挙動

#includeの行がヘッダファイルの内容へ置き換えられる

#ifndef ~ #endif

当該ヘッダファイルが複数回includeされてしまうのを防ぐために使われる。 以下が例。

#ifndef _HOGE_H_
#define _HOGE_H_
(インクルードしたい内容)
#endif

#define で_HOGE_H_が宣言されていなかったら#ifndef ~ #endif間が実行される。 _HOGE_H_をinclude実行のフラグとして使っている感じ。

stdint

固定サイズの整数型を提供するライブラリ。

int32_t
int64_t

みたいな長さ決め打ちの整数型を利用できる。 最近はあまり意識しなくてもよいはずだが、intは環境によって16ビットだったり32ビットだったり64ビットだったりする場合があるため明示的に指定できるようにこのライブラリが用意されている。

_tってそういえば何

typedefで作った型の名前に慣習的につけるもの

0をポインタ型にキャストすると

null ポインタになる

ポインタ型をifの条件節に渡すと

  • null ポインタの場合false
  • それ以外はtrue として判断される

getopt

en.wikipedia.org

optionをgetするということなので、コマンドに追加で引っ付けられるオプションを解析するもの。

実際使うときはこんな感じ(上記wikiより引用)

#include <getopt.h>

int getopt_long(int argc, char * const argv[],
                const char *optstring,
                const struct option *longopts, int *longindex);

getoptはoptstringで指定したコロンの数によってオプションの性質を決められる。

オプション文字の直後に

  • コロンが無い→引数無し
  • コロンが1つある→引数アリだがなくても良い。(つまりデフォルト値が存在する)
  • コロンが2つある→引数必須

という感じ。

例えば

abc:d::

となっている場合、 a,b,c,dの4つのオプションがあり、a,bは引数無し、cは引数ありだがなくても良い、dは引数必須、ということが分かる。https://en.wikipedia.org/wiki/Getopt

sigset_tの初期化パターン

sigset_tは内部的にはunsigned long int の配列でシグナルを保持できる。 sigset_tの初期化パターンは2通り覚えておく

sigfillset後に絞り込みパターン

全てのシグナルをsigset_tに突っ込んで捕捉し、その後デフォルト動作をさせたいものだけ適宜除外してから利用する方式。 割り込み絶対許さないマンになりたいときなどはこっちを使えば楽。

sigemptyset後に追加パターン

取り敢えずシグナルは無しの状態で初期化し、シグナルを適宜後から追加して補足する 特定のシグナルを受け取った時だけ動作を変えたいパターンの時はこっちが有効。

C言語でハッシュテーブル

<search.h>を使えば実現できる

linuxjm.osdn.jp

hserach_data型の構造体変数を用意し、hcreateやhcreate_rで初期化すればhashを扱う準備が整う。 _rがついている方は複数のハッシュテーブルを利用することができるので、そちらを使う方が都合が良いことが多い。 hashテーブルをホワイトボード的に使う場合など単一の方が都合が良いときはr無しの方を使えば良さそう。

hsearchに渡すのはENTRY型じゃなく文字列ではだめ?

searchという名前がついているのに検索する際にはchar型配列ではなくENTRY型変数(keyとそれに対応するdataを持つ構造体)を利用するのは少し違和感がある。key文字列だけでもよいのではないかと思っちゃう。

実際のところ、この関数はsearchだけかinsertもやるのかを選択できる。第二引数において、FINDを指定した場合はsearch, ENTERを指定した場合はsearch + insertという挙動※になる。 insert処理を実行することを念頭にプログラムを組むならENTRY型でkey-valueをセットで渡す必要があるため、そこまでカバーすることを考えてENTRY型になっている。

serachが成功したならENTER指定だろうとinsertは実行されないことに注意する。POSIXで決まっている。

timeval構造体

select などの一定時間監視するタイプの関数に引数の型として指定されていることが多い。

long tv_sec 時間の整数部。

long tv_usec 時間の小数部。マイクロ秒単位。

ミリ秒単位で利用するには

表現したいミリ秒の整数値を用意し、その整数を

  • 1000で割ったものをtv_sec
  • 1000で割った余りを1000倍したものをtv_usec

に代入すればよい。 1000で割った余りは必ず3桁以下になり、それを1000倍すると必ず高々6桁の整数になるので、ミリ秒をマイクロ秒に変換したものを表現できる。

typedefの考え方

「普通に変数を宣言するつもりでコードを書き、頭にtypedefをつければ変数の部分を型名として新たな方を定義できる」と考えるとわかりやすい

qiita.com

FD_ISSET(0, &rfds)とselect

linuxjm.osdn.jp

FD_SETを実行後にFD_ISSETで確認を行うと、基本的にはtrueが返ってくる。 ただし、selectを実行してrfdsに含まれる識別子が示すファイルを監視中に該当ファイルに対して接続が発生しなかった場合はfalseとなる。

このため、listenソケットやacceptソケットを生成してファイルディスクリプタがそれぞれのソケットに割り振られている状態だったとしても、接続が発生していなければFD_ISSETはfalseを返す。 確認用プログラムが以下。大分部を上記リンクのサンプルプログラムから流用。

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int
main(void)
{
    fd_set rfds;
    struct timeval tv;
    int retval;

    /* stdin (fd 0) を監視し、入力があった場合に表示する。*/
    FD_ZERO(&rfds);
    FD_SET(0, &rfds);

    /* 5 秒間監視する。*/
    tv.tv_sec = 5;
    tv.tv_usec = 0;

    retval = select(1, &rfds, NULL, NULL, &tv);
    /* この時点での tv の値を信頼してはならない。*/

    if (retval == -1) {
        perror("select()");
    }
    else if (retval)
    {
        printf("今、データが取得できました。\n");
        printf("select後かつデータ取得成功時のFD_ISSET: %d\n", FD_ISSET(0, &rfds));
        /* FD_ISSET(0, &rfds) が true になる。*/
    }
    else
    {
        printf("5 秒以内にデータが入力されませんでした。\n");
        printf("select後かつデータ取得失敗時のFD_ISSET: %d\n", FD_ISSET(0, &rfds));
    }
    exit(EXIT_SUCCESS);
}

出力

失敗時

select前のFD_ISSET: 1
5 秒以内にデータが入力されませんでした。
select後かつデータ取得失敗時のFD_ISSET: 0

成功時

select前のFD_ISSET: 1
a
今、データが取得できました。
select後かつデータ取得失敗時のFD_ISSET: 1