エキスパートCプログラミング

エキスパートCプログラミング―知られざるCの深層 (Ascii books)

エキスパートCプログラミング―知られざるCの深層 (Ascii books)

良い本だった。この本を学生時代に読んでいたら専門が変わっていたかもしれない。

  • 型の違う場合の演算や比較
    • 異なる型同士を演算あるいは比較する場合は、きちんとキャストすること。
  • unsigned は気軽に使わない。
    • 「値が負になることは無い」というだけで使うのは危険だ。
    • ビットフィールドやバイナリマスクだけに限ること。
    • なぜなら
      • unsigned int型 と (マイナスの値を持つ)int型 を演算あるいは比較した場合、全く反対の値が得られることがある。
      • ANSI Cでは、情報欠落を防ぐため、int型に変換される。
      • K&R Cでは、unsignedに揃えるため、-1はffffffffに変換される。
  • const修飾 は readonlyの意味
  • 勘違いしやすいCの仕様
  • 勘違いしやすいCの文字列
    • NUL: ASCII文字の一つビットパターンのゼロ
    • NULL: 何も指さない特殊なポインタ
  • 関数が見えすぎる(Cの欠点)
    • Cの関数はデフォルトでグローバルスコープになる。
    • 関数の頭に"extern"をつけても良いが書かない場合との違いは無い。
    • "static"をつけると、スコープはその「ファイル」に制限される。
    • ライブラリを作る際、他からアクセス可能にするには、グローバルスコープにするしかない。シンボルは完全に公開するかまったく公開しないかのどちらか。つまり、オールオアナッシング。
  • 複数の意味を持つCのシンボル(Cの欠点)
    • static
      • 関数内では、「関数呼び出しの間にも、その値を保持せよ」
      • 関数自体に対しては、「関数のスコープをファイル内に限定せよ」
    • extern
      • 関数の場合、「グローバル関数である」(デフォルトなので冗長な宣言)
      • 変数の場合、「どこかで定義してある」
    • void
      • 関数の戻り値の場合、「値は返さない」
      • ポインタ定義の場合、任意の型へのポインタ
      • 引数リスト内では、「引数無し」
  • 関数の引数は、右から左の順にスタックにプッシュされる?
    • No.
    • 可能な場合には、速度向上のために引数はレジスタ渡しとなる。
    • int型の引数は普通レジスタ渡しになるが、構造体(中身がintだけでも)ならスタック渡しになることが多い。
  • cdecl
    • Cの宣言を解読して英語表現になおすプログラム
    • 手元の環境には入っていない。
  • 定義と宣言の違い
    • 定義 (definition): 一回しか記述できない。オブジェクトの型を指定し、必要な領域を確保する。新規オブジェクトの作成に使う。
      • 例: int my_array[100];
    • 宣言 (declaration): 何度でも記述できる。オブジェクトの型を記述する。どこか(たとえば別ファイル)で定義されたオブジェクトを参照する。
      • 例: extern int my_array[];
  • 配列とポインタの違い
    • ポインタ
      • データのアドレスを格納
      • データは間接参照される。最初にポインタの内容が取得され、それからその内容が参照される。
      • ポインタに、添字[i]がついている場合には、先に"i"の内容も参照される。
      • 普通は動的なデータ構造で使われる。
      • 普通はmalloc(), free()などと一緒に使われる。
      • 普通は名無しのデータを指す。
    • 配列
      • データを格納
      • データは直接参照される。a[i]では、iの内容を参照するだけでよい。
      • 普通は特定要素数の同じ型のデータの格納に使われる。
      • 自動的に確保、削除される。
      • それ自身、名前の付いた変数である。
  • 文字列による初期化
    • ポインタ
      • ポインタの定義で確保されるのはポインタ自身の領域だけで、それが指すものの領域は確保されない。ただし、文字列によって初期化される場合は例外
      • char *p = "breadfruit";
      • ANSI Cではこのとき、文字列は書き込み禁止になる。(static変数としてdata segmentに値が入るから?)
    • 配列
      • 同様に文字列による初期化が可能。ただし書き換え可。(stack segmentに値が入るから?)
      • char a[] = "gooseberry";
    • 良くある文字列郡の定義方法
  • ライブラリの中のシンボルを調べる

% foreach i (lib?*)
? echo $i
? nm $i |grep xdr_reference | grep -v UNDEF
? end

  • コードとデータ
    • コード: コンパイル
    • データ: 実行時
    • Cではコードとデータをはっきり区別している。
  • a.out
  • 以下で、data, bss, textのサイズが確認できる。
    • % size 実行ファイル
  • dataセグメント: 初期化されたglobal dataあるいはstatic dataが格納される。
  • bssセグメント: 未初期化globalデータに必要な"サイズ"が格納される。実行時にそのサイズが確保され、ゼロクリアされる。
    • なお、ローカル変数は、実行ファイル(a.out)には格納されず、実行時にスタックセグメントに作成される。
    • 再帰呼び出しの無い言語であれば、動的なスタックは必要なく、ローカル変数や引数、関数の戻り値に必要な領域のサイズはコンパイル時に決めておけば良い(初期BASIC,COBOL, FORTRAN等)
    • ヒープは、dataセグメントからアロケートされる。ヒープ内の全てのデータには名前が付いていない。必ずポインタ経由でアクセスすることになる。
  • setjump, longjump
    • 関数の外へジャンプできる。setjumpによってPCとstack位置をセーブする。longjumpがその値を復元する。(stackのunwindと呼ばれる処理)
    • C++では、setjump,longjumpが、より一般的な例外機構であるcatch, throughに発展した。
  • スタックフレーム
    • 関数呼び出しの際の管理用データのこと
    • 普通はスタックにプッシュされるが、高速化のため、特別なレジスタ(レジスタウィンドウ)に格納されることもある。
  • bus error, segmentation fault
    • どちらも同じような意味だし、OSによって正確な意味も異なる
    • bus error: ミスアライメントを主"原因"とするエラー
    • segmentation fault: 異常なポインタ参照という"結果(症状)"によるエラー
      • 1. 間違ったポインタ値
      • 2. mallocエリア外へのアクセス
      • 3. free()のミス
  • C
    • 10, 8, 16進数の定数は記述できるが、2進数の定数は記述できない。不便。
  • 型の格上げ charはintになる
    • 式の中では、charは自動的にintになる。sizeof 'A'の値は1でなく4(確認済)。
    • 引数としてchar型を取るものはそのままcharです(当然)
  • Unixで端末をrawモードで利用する簡単な方法

system("stty raw"); /* system("stty raw -echo"); */
c = getchar();
system("stty cooked"); /* system("stty cooked echo"); */

  • 関数へのポインタ配列
    • 宣言:

void (*state[MAX_STATES]);

    • 呼び出し:

(*state[i])();

あるいは

state[i](); /* ANSI Cでは同じ意味!*/

  • 値渡し, 参照渡し
    • Cでは原則すべて値渡し
    • ただし、配列と関数だけは例外で参照渡し。
    • 配列を引数として関数に渡すコードにおいて、配列は、コンパイラによってポインタに変換される。結果として配列は参照渡しとなる。
  • char文字列へのポインタの配列
    • char *pea[5];
  • a.out
    • assembler outputの略
  • ++i, i++の違い
    • コンパイラの中間コードの用語を使った説明
      • ++i: xの位置を取得し、その内容をインクリメントして、その値をレジスタに持ってくる
      • i++: xの位置を取得し、その値をレジスタに持ってきてから、メモリの中のxの値をインクリメントする。
    • ライブラリコール
      • 全てのANSI Cで共通
      • ライブラリ中のルーチン呼び出し
      • ユーザプログラムとリンクされる
      • ユーザのアドレス空間で実行される
      • 「ユーザ」タイムの一部としてカウントされる
      • ルーチン呼び出しのオーバヘッドが小さい(システムコールを呼び出していなければ)
      • libcには約300のシステムコールあり
      • man のsection 3
      • system, fprintf, malloc, send, recv,...