3-2 共有体

共用体は、複数の変数や配列を同じ記憶域を使用するようにまとめたもの。各項目に同時にアクセスすることは出来ない
以下に説明するような用法がある。


3.3.1 記憶域の効率的な使用

同じ記憶域を複数の目的に使用し、メモリを節約するために共用体を使う。メモリの増大と共に使われる頻度は減っているが、非常に大量のオブジェクトが使用されるような場面では、現在でも用いられている。


3.3.2 多態の実装

最も多く見られる使用目的。
ひとつのオブジェクトだけを使って、様々な型を表現したいとき用いられる。構造体と組み合わせて使われることが多い。

// 構造体の識別番号
enum subject_type {
JAPANESE = 0;
MATH = 1;
ENGLISH = 2;
SCIENCE = 3;
CIVICS = 4;
}
struct subject_inf{
enum subject_type type; // 保持している科目の種類(型、type)を保持
union{
struct s_japanese japansese;
struct s_math math;
struct s_english english;
struct s_science science;
struct s_civics civics;
} sb;
}


様々な内部表現による操作

ある変数aの内部表現に直接アクセスしたいとき、その変数aと、その内部表現にアクセスするための変数を共用体で宣言する。以下はIEEE754形式で保存されているfloatの各要素に個別にアクセスするための共用体。

union float_int{
float a;
struct{
unsigned int mant : 23; // 仮数部
unsigned int exp : 8; // 指数部
unsigned int sign : 1; // 符号部
} s;
}

-練習問題

練習問題より、コロンでbit定義が出来ることに感動したので、これの検証をする。

以下のプログラムを試してみる。


include

struct hoge {
> int a : 1;
};

int main(void){
> struct hoge hoge1;
> hoge1.a = 0;
> printf("hoge = %x\n", hoge1.a);
> printf("hoge = %x\n", ++hoge1.a);
> printf("hoge = %x\n", ++hoge1.a);
}

結果
hoge = 0
hoge = 1
hoge = 0xffffffffe

struct hogeのaをintで(signedで)宣言すると、変な値になる。
unsignedで宣言するようなのかな?
そもそもなんでstruct内でないとコロンを使えないのだろう(local変数でやってみるとだめだった。)

3-2 構造体

-内容のまとめ

書いてあった内容の概要
構造体は・・・

  • データ要素のグループ化できる

  • 関数からの戻り値を複数返すことが出来る

  • データ構造を表現することが出来る

  • オブジェクト指向プログラミングが出来る

  • -練習問題

    (3.6)構造体に配列を埋め込む方法を使えば、構造体が持つデータ要素を配列型とし、関数に対してその構造体を引き渡すことで、配列のデータをポインタとしてではなく、値として直接引き渡すことが出来る。弧の方法を使用している例を探し出しなさい。この方法があまり使われないのはなぜか。理由をいくつか挙げなさい。
    配列が全部渡されてる!?まじ!?ということでテストプログラム書いて確かめてみた。

    プログラムのコード


    #include
    #define FOR(j) for(i=0; i < j; i++)

    int i;
    struct st{
    int A[100];
    int B[100];
    };

    void pass_st(struct st st1, int *pa, int *pb){
    printf("mainのst1.Aの先頭アドレス = %p\n", pa);
    printf("mainのst1.Bの先頭アドレス = %p\n", pb);
    printf("pass_stのst1.Aの先頭アドレス = %p\n", st1.A);
    printf("mainとpass_stとの差 = %d\n", pa - st1.A);
    }

    void pass_box(int A[], int *p){
    printf("mainのst1.Aの先頭アドレス = %p\n", p);
    printf("pass_boxのst1.Aの先頭アドレス = %p\n", A);
    printf("mainとprint_stとの差 = %d\n", p - A);
    }

    int main(void){
    struct st st1;
    int A[100];

    pass_st(st1, st1.A, st1.B);
    pass_box(A, &A[0]);
    return 0;
    }

    実行結果


    [/CodeReading/sec3$]./a.out
    mainのst1.Aの先頭アドレス = 0x7fff5fbff500
    mainのst1.Bの先頭アドレス = 0x7fff5fbff690
    pass_stのst1.Aの先頭アドレス = 0x7fff5fbff1e0
    mainとpass_stとの差 = 200
    mainのst1.Aの先頭アドレス = 0x7fff5fbff820
    pass_boxのst1.Aの先頭アドレス = 0x7fff5fbff820
    mainとprint_stとの差 = 0
    [/CodeReading/sec3$]

    まじだった。へぇ

    3-1 ポインタ

    -内容のまとめ

    書いてあった内容の概要

  • ポインタでリンクデータ構造を作れる!(3.4節で詳説)

  • データ構造の動的割り当て

    • # define new(type) (type * ) calloc(sieof(type), 1)というマクロを定義すると便利
  • 参照呼び出し
    • 関数の引数で、構造体はポインタで参照渡しを使い、double, int等は直接渡す
    • 関数の引数にconstをつけることで、メモリ効率のために参照渡しをしているこ>とを明示できる
  • データ要素へのアクセス
    • 配列操作には、ポインタによる配列操作と、インデックスによる配列操作がある
      配列による引数と戻り値
    • 配列を関数の引数として渡すと、参照渡しとなるので注意が必要
    • 関数の中でローカル変数として配列を定義し、それを引数とすると、関数終了時にローカル変数がなくなってしまうため、だめになる。だから、staticを付けておくといい。
    • staticをつけても、同じ配列にアクセスすると上書き等の不具合がおきるので、ラッパー関数をつくって対処するといい。
    • ラッパーは、static配列を返り値とする関数の返り値を保持しておく役割を持つ。


    3.1.6 関数のポインタ

    関数の引数として関数を渡したいとき、直接関数そのものを渡すことはできないので、代わりに「関数へのポインタ」を用意し、それを参照渡しする。


    3.1.7 ポインタによる別名参照

    複数のデータから、状況に応じてひとつを選んで処理するようなとき、選んだデータを指定するのにポインタを使う。以下のような場合が挙げられる:

    • 選んだ値や構造体をコピーする(つまり、変数に格納する)のではなく、ポインタで指して操作したほうが速いと思われるとき。
    • 静的に初期化されたデータ(ソース内に直書きされた文字列リテラルなど)をデータとして指定するとき。
    • 複数の関数から操作される複数のデータを、ひとつの名前で扱う目的で、グローバルにポインタを宣言し、それを各変数から相互に操作するような場合。


    3.1.8 ポインタと文字列

    char型へのポインタ(文字ポインタ)とchar型の配列(文字配列)は、同じ目的で使用されることが多いが、振る舞いが一部異なるので、常にそれを意識しておく必要がある。

    • 文字列リテラルでこの2つを初期化するとき、代入先が文字配列の場合のみ、最後に"\n"が付加される。
    • extern修飾子をつけられた文字ポインタは、別の文字ポインタにアクセスすることは可能だが、別の文字配列に対して正しくアクセスすることはできない。しかも、そのように間違った宣言をしておいても、コンパイラはエラーを出さない。


    3.1.9 ダイレクトメモリアクセス

    メモリ上のある特定の番地群が、CPUやその他のデバイスとのデータの共有場所として定義されている場合がある(xx番地に文字列を書きこむと、それをBIOSが読んで画面に出力してくれる、とか)。そのような場合、指定した番地を指すポインタを定義して、読み書きに使用する。組み込みシステムやカーネルデバイスドライバなどによく登場する。


    -練習問題

    (3.4)Cのポインタとメモリアドレスの違いは何か。それはコードを理解することにどう影響するか。この違いを利用したツールにはどんなものがあるかを述べなさい
    Cのポインタはメモリアドレスを参照するので、同じようなものととらえることができる>。しかし、基本的にポインタは実行中のプログラム用に割り当てられた領域しか参照でき
    ず、例えばハードウェアを直接いじるようなポインタを使うことはできない。(設定すれ>ばできるらしい。) Cのポインタはp*等名前を付けることができるので、直接メモりアドレスを扱うよりも>断然扱いやすいし、コードを理解する上で読みやすい。
     どのようなツールが存在するか・・・ポインタとメモリアドレスの違いを利用するツールといえば、OSがsegmentation faultを制御する等しか思いつかないw
    ~

    -内容のまとめ
    書いてあった内容の概要

  • 'a' - 'A'はascii以外は問題が生じるからよくない

  • p* - '0'は文字から整数値を求めるイディオム

  • コードの数式が複雑な論理で構成されている場合は、ドモルガン等で式を簡略化してみる

  • 短絡評価法(&&は偽の式が現れた時点で全体を偽、||はその逆とする)

    • Cでは短絡評価法を仮定してかまわないが、言語によってはコンパイラによる。



    -練習問題


    IOCCC(世界邪悪なCコードコンテスト)のプログラムを読んでみた(wiki掲載の円周率を求めるプログラム)。
    リファクタリングってか、読みやすいコードの重要性を感じた瞬間でした笑
    第3章が気になって仕方ないので、残りの2章はスキップします!

    全演習問題やってると時間がかかってしまうので、今後はやる演習をしぼっていきたいとおもうがどうだろう?

    -内容のまとめ
    書いてあった内容の概要


    -練習問題

    gotoステートメントを使用しているコードを5つ探し出し、用途>別に分類しなさい。(前述した用途ごとに少なくとも1つの例が入るようにコードを探し
    なさい)。それぞれのgotoについて、それをループステートメント等に置き換えることが
    できるかどうか、また、そうすべきかどうかを論じなさい。
    gotoとか今時使ってるコードなんてあるのか?w
    とか思ってたけど、/usr/src/bin のフォルダ全部チェックするスクリプト作って走らせ
    てみたらすげえ出てきたw

    もちろんexpand.cにもあったが、毎回同じでもつまらないのでchio.cを見てみる。
    chioはメディアチェンジャの動作を制御するコマンドらしい。

    一部抜粋すると、

     if(argc > 4){
    ・・・
    goto usage:
    }

    このように、usageを表示させるのを関数ではなくgotoでラベルを付けて実装している。個人的には、goto usageではなくusage関数を作って呼び出す派。

    べつに見てわかりにくくないからいいと思う。gotoは使ったことが無いので、その影響についてはいまいち実感が無いw
    ~

    -内容のまとめ
    書いてあった内容の概要

  • 'a' - 'A'はascii以外は問題が生じるからよくない

  • p* - '0'は文字から整数値を求めるイディオム

  • コードの数式が複雑な論理で構成されている場合は、ドモルガン等で式を簡略化してみる

  • 短絡評価法(&&は偽の式が現れた時点で全体を偽、||はその逆とする)

    • Cでは短絡評価法を仮定してかまわないが、言語によってはコンパイラによる。



    -練習問題


    (2.20)
    GNUcoreutils-8.5に含まれるprintf関数。
    65-68行目に、文字列の単純な大小比較がある。

    文字コードを統一するか、専用の関数を使って文字列比較をそれに任せてしまえばいいのでは。


    (2.21) ソースコードベースから自明でないブール式を5つ探し出し、簡約後のブール式で推論
    しなさい。式の要素が何を表しているかはあまり考えないで、式の真偽を決める条件に集
    中するようにしなさい。短絡評価法を積極的に使用しなさい。

    lsのソースコードより

     if( (p = getenv("COLUMNS")) != 'NULL' && *p != '\0')
    ドモルガンの法則より
     if( !(p = getenv("COLUMNS")) == 'NULL' || *p == '\0')

    ゆえに
    p = getenv("COLUMNS")がNULLまたは終端文字でない時に実行する



    ・・・面倒だ。一個だけで大丈夫か?

    -内容のまとめ
    書いてあった内容の概要です

    • continueはswitchステートメントを無視する。
    • continue及びbreakはifステートメントに作用しない。
    • ループの中身が空でいい場合、プレースホルダとしてcontinueが置かれることがある。
    • Javaではbreak、continue文の後にラベル識別子を書ける。実行されると、対応したラベルの位置へ処理が移る。ネストされたループを脱出するときに用いる。
    • ネストされたループがなくても、対応をはっきりさせるために、ラベルを付けることがある。

    -練習問題


    (2.19) 本書に掲載されているソースコードを調べ,breakとcontinueを10個探しなさい。
    それぞれのケースについて、対応するステートメントが実行されたときに制御が移る位置を示し、素のステートメントが使われる理由を説明しなさい。ここでは、あえてコードのロジックを完全に理解することはない。ステートメントの使用パターンに基づいて説明するだけで十分である
    expand.cから抜粋する。!!!がkattunが入れた回答
    	while ((c = getopt (argc, argv, "t:")) != -1) {
    switch (c) {
    case 't':
    getstops(optarg);
    break; // !!! switch文を抜ける
    // !!! caseが見つかったから、他のオプションを解釈する必要が無いため
    case '?':
    default:
    usage();
    /* NOTREACHED */
    }
    }
    do {
    if (argc > 0) {
    if (freopen(argv[0], "r", stdin) == NULL) {
    warn("%s", argv[0]);
    rval = 1;
    argc--, argv++;
    continue; // !!! do文の先頭に戻る。
    // !!! 引数のエラーのため、これを処理する必要がないから
    }
    curfile = argv[0];
    argc--, argv++;
    } else
    curfile = "stdin";
    column = 0;
    while ((wc = getwchar()) != WEOF) {
    switch (wc) {
    case '\t':
    if (nstops == 0) {
    do {
    putwchar(' ');
    column++;
    } while (column & 07);
    continue; // !!! while文の条件式の評価に戻る
    // !!! tabの空白8文字の処理が終了したため。
    }
    以下省略
    }
    }
    } while (argc > 0);


    10個は多いので,ここらへんで。。。