パスワードを忘れた? アカウント作成
1087698 journal
プログラミング

EarOwlの日記: [C言語] 構造体を使う 33

日記 by EarOwl

例として、気温・湿度・気圧・風向・風速の 5つの int 型のデータを 100回分保存するバッファを考える。

よく見かけるのが、

int weather_data[100][5];

という形で確保して、

weather_data[n][0] = temp;
weather_data[n][1] = hum;

という風に保存するという書き方。 (もっと酷いものだと int weather_data[5 * 100]; なんていうのもあるが…。)

これだと、『1回分のデータ配列中の何番目にどのデータ (気温・湿度…) が保存されているか』ということがソースコード上で明確でなくなってしまう。

配列のインデックスとデータを対応付ける #define を

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
  • by miyuri (33181) on 2011年12月20日 11時42分 (#2068965) 日記

    TMPR[100];
    HMDT[100];
    ATMS[100];
     :

    年寄りだと、こういう感じにしそう。

    • by soltiox (25610) on 2011年12月20日 15時18分 (#2069091) 日記

      int tmp000, tmp001, tmp002, .. tmp099;
      int hum000, hum001, hum002, .. hum099;
      ...

      え? 特定のデータにアクセスするのが難しいって?
      何のためにポインタ変数があるんだ!

      // 途中で、間違い探し的に変数名が変わるのは仕様

      //// 10個の変数ずつ定義した行をコピペして、10の位だけ書き換えれば
      //// 変数名間違えにくいし、効率的だね!

      親コメント
    • by EarOwl (24188) on 2011年12月20日 16時08分 (#2069112) 日記

      データの扱い方によっては…具体的に言うと、『1回分のデータ』をセットで扱うことが無く、むしろ気温は気温だけで、湿度は湿度だけでといった具合にデータの種類毎に扱うような場合はむしろそちらの方が適切ということも有り得ますね。

      『適切なデータ構造』というのは、そのデータがどのように扱われるのかにも依存するということで。

      親コメント
  • あまりメモリサイズを気にしなくてよいとか、メモリなんて腐るほどあるわいとか、そういう世界では構造体の配列を使うのが正解。

    ところが、「メモリの1bitは血の一滴」などという世界では構造体は怖くて使えない。特に今回は構造体の要素が5個…つまり 2^n の数ではない個数存在する。
    メモリがキツキツである事をしらないコンパイラは、alignment をきれいにするために:

    typedef struct {
        int temperature;
        int humidity;
        int pressure;
        int wind_direction;
        int wind_speed;
    } weather_data_t;

    typedef struct {
        int temperature;
        int humidity;
        int pressure;
        int wind_direction;
        int wind_speed;
     
        int padding1;
        int paddint2;
        int padding3;
    } weather_data_t;

    であるかのように確保してくれる(もちろん、本当は padding1, padding2, padding3 へはアクセスできない)。

    コンパイラに「メモリキツキツだからっ、無駄遣いしないでっっ」と指示できるコンパイラならいいのだが、指示できないコンパイラ(あるいは指示できるのだが、コンパイラの我が強くて言う事を聞かない場合)は、[100][5] の配列を作るしかないのである。

    --
    fjの教祖様
    • そういう世界で使うコンパイラは、境界合せをするのだろうか。

      親コメント
      • by Anonymous Coward

        そういう世界で使うコンパイラは、境界合せをするのだろうか。

        word-aligneという意味なら、普通は境界合わせしますよ。
        構造体のメンバにintが居ればintに, long longが居ればlonglongにというように。
        詰める必要があるなら、 __attribute__((packed))とか __packedとかで修飾。問題は標準がなくコンパイラに合わせる必要があること。

        指示できないコンパイラ(あるいは指示できるのだが、コンパイラの我が強くて言う事を聞かない場合)

        は、この世界では使い物にならないでしょう。

        • この世界では使い物にならないでしょう。

          それが必ずしもその一言で消失してくれない辺りが鬱陶しい。

          「ポインタ動かす所は別にすればいいじゃん」
          攻撃はよくある。もちろん、山盛りのバグ付きで。

          最も頭痛がするのは、通信ライブラリ周りで、little endian を big endian に直すのに構造体に値を入れる過程で htonl(3)とかを呼べばいい、と思い込んでいるコードが絡んでくる場合で…padding だけでも結構頭痛が…

          # だれか教えてくれ。Cの構造体のエレメントのメモリ展開時の順序は本当に保証されているのか??

          --
          fjの教祖様
          親コメント
          • 最も頭痛がするのは、通信ライブラリ周りで、little endian を big endian に直すのに構造体に値を入れる過程で htonl(3)とかを呼べばいい、と思い込んでいるコードが絡んでくる場合で…padding だけでも結構頭痛が…

            どういう場面?
            書き方によってはバッファから読むところで例外が出る事はあるけど、(int境界に合わせてある普通の)構造体特有の問題はあるのかな。

            親コメント
            • プロトコルヘッダーをバイト列に変換したり、逆にバイト列をプロトコルヘッダーの構造体に直す所。

              ソケット周りの実装を読むと、途中で構造体へのポインタがバイト列へのポインタにすり変わったり、その逆が起こったりしているものが多い。
              大昔のコードが padding とかを一切しなかったので、ポインタのすり替えでできたのだが、その「伝統」が受け継がれてしまっている。

              --
              fjの教祖様
              親コメント
          • by Anonymous Coward

            > # だれか教えてくれ。Cの構造体のエレメントのメモリ展開時の順序は本当に保証されているのか??
            http://portable-c.jugem.jp/?eid=17 [jugem.jp]
            JIS X 3010:2003を実際に読んで裏をとるべきだが、とりあえずググッた結果を鵜呑みにする限りでは保証されているらしい。
            # コンパイラが規格準拠でない可能性まで考慮してるなら知らんとしか

    • by Anonymous Coward

      あるデータ型に対して、効率よくアクセスできる(ものによっては Bus error を起こさない)ように、変数を詰め込むだけだから、この場合は padding は入らないと思うけどなぁ。
      そりゃぁ、2^n に align すれば効率は良いだろうけど、それはかなり過剰だと思う。

  • by Anonymous Coward on 2011年12月20日 11時43分 (#2068967)

    データ構造の詳細はどうでも良いことで、重要なことはインターフェースを明かにすること。
    インプリメントを行う *.c ファイルには、

    static int weather_data[5 * 100];

    であっても良い(センスを感じられないのは確かだが)。
    でも、static は外せない。詳細はどうでも良いことで、外部に見せる必要がないから。

    インターフェイスを宣言する *.h には、
    - データ構造の初期化関数
    - 後始末関数
    - 値を設定する関数
    - 必要に応じてデータ構造から値を取得する関数
    - データ構造を使って計算する関数
    のプロトタイプ宣言だけを書いておけば良い。

    • グローバル変数は static にして外部へは公開せず、インターフェース関数を公開するというのは確かにその通りですが、今回の日記ではそこが主眼ではないので static は付けませんでした。

      それよりも『データ構造の詳細はどうでも良いこと』という主張には疑問です。

      単に正しく動作させるというだけなら配列だろうが構造体だろうが問題となることはありませんが、保守性や流用性を考えたとき、適切なデータ構造を選択するということは重要になってくると思います。

      実際、データ構造が適切でないために、それらのデータを扱う処理の記述が複雑になり、取り除くことの難しいバグの温床となっているソースコードは今までに数多く見てきました。

      今回のように単純な例ならバグに繋がることは少ないと思いますが、常に適切なデータ構造を選択するよう心掛けることが、より複雑なデータを扱う際にバグを減らすことに繋がると考えます。

      親コメント
    • by Anonymous Coward

      static キタ───(゚∀゚)───!!

      それは一番やっちゃいけない実装。
      バッファサイズを100回分に限るのもいけない。
      スタックサイズがあまりに小さくヒープを切るにも制限が多い等、合理的な理由があるなら話は別だが。
      いいかげん観念してオブジェクト指向しちゃいなyo!!

      • オブジェクト指向を意識した実装に見えるが。

        --
        1を聞いて0を知れ!
        親コメント
        • by Anonymous Coward

          複数オブジェクトを作ったら気づかない間に相互干渉する仕様がオブジェクト指向的なんですか。オブジェクト指向ばんざい。シングルトンにするならいいかもしれないけどそんな前提条件どこにも書かれてないし。

          • > データ構造の詳細はどうでも良いことで、重要なことはインターフェースを明かにすること。
            って書いてあるだろ。
            グローバルにすべきかどうかは自分で考えりゃいいし、気づかない間に相互干渉するのであればインターフェースが明かとは言い難いわな。

            --
            1を聞いて0を知れ!
            親コメント
            • by Anonymous Coward

              じゃあインスタンスを同時に使えるかどうかというインターフェースをヘッダファイルで明らかにできないC言語に欠陥があるってことだな。
              # コメントで人間向けに書いておけばいいとかいうなよ。

              • > # コメントで人間向けに書いておけばいいとかいうなよ。
                言うに決まってるだろ。
                読まない馬鹿が使うと困るって言いたいのなら #2068967 が挙げている「データ構造の初期化関数」を利用して複数オブジェクトを作ることを阻止するってことくらい、プログラマなら誰でも思いつくよな。

                それに、欠陥つーのも違うだろ。そもそもC言語がオブジェクト指向に向いていないだけ。

                --
                1を聞いて0を知れ!
                親コメント
        • by Anonymous Coward

          元の人はJavaのstaticとC言語のstaticを混同してるんじゃないの?
          もちろんJavaの場合でもstaticを多用するのはあんましよくない。

          ちなみにC言語の高速化テクニックの一つとしては
          int w[500]
          的なのはありました。
          シーケンシャルに順番にアクセスして処理できる場合はそっちの方が早くなるから。

          メモリアクセスの最適化の問題なので、そういう書き方をした方が今でも早くなりそうだけど、
          汚いコードでバグが出やすくなるから、まあ、最初は普通に構造体使って書いておいて、
          プロファイルしてここがボトルネックだというのなら、それから書き直しても遅くはない。

  • by Anonymous Coward on 2011年12月20日 13時46分 (#2069043)

    この場合はintだから大抵の処理系で大丈夫だろうけし、パフォーマンスやメモリ使用量を気にする用途かそうでないかにもよるだろうけど。

    • もちろんパフォーマンスやメモリ使用量の制約がある場合は、それに応じた形にする必要がありますが、それはあくまでも『特殊な条件』という意識が必要なのではないかと思います。

      一般的にはやはりバグを生まないことが重要だと思い、そのために読みやすさ、間違いの犯しにくさを実現する方法を考えています。

      親コメント
      • その条件だと「Cを使うな」の一択に…

        メモリ: たっぷりあります
        CPU: ちょっとやそっとではフルパワーになりません

        などという特殊条件用の言語ではありませんから、Cは。

        --
        fjの教祖様
        親コメント
        • C言語が使われるのがほぼパフォーマンスやメモリ使用量の制限の厳しい条件下だとしても、コードの全てにそのような条件でのチューニングが必要とは限らない、むしろチューニングが必要なのは一部のコードのみとなるケースがほとんどなのではないでしょうか。

          例えば処理速度なら、『プログラムの処理にかかる時間の80%はコード全体の20%の部分が占める』 [wikipedia.org]とすれば、処理速度の要求がある程度厳しい場合でも、コード全体のうちおよそ 80% についてはチューニングは不要であることが多いと思われます。

          チューニングが必要な部分と不要な部分で別の言語を使うべきとまでは言わないですよね?

          親コメント
        • by Anonymous Coward

          むしろ、そういう条件がそう特殊でもなくなってきてるのに何故か皆

          「C/C++使いじゃないとホンモノのプログラマじゃない」

          みたいなマッチョイズムに囚われてて使わなくても良い所にC/C++使って不幸になってる事が多いような…

          • >むしろ、そういう条件がそう特殊でもなくなってきてるのに
            そうとも言い切れないような。

            今日も
            >>メモリ: たっぷりあります
            >>CPU: ちょっとやそっとではフルパワーになりません
            と思ってる人間が書いた糞遅いコードを修正したいけどできない場面に遭遇してきたところです。

            たしかに数倍くらいなら遅くてもなんとかなる場合も多いけど、
            数百倍、数千倍、数万倍遅いコードを書かれても平気なほど、
            メモリもCPUパワーもバッテリー容量も潤沢なわけではないぞ。

            そういえば、冬場なのにどこかのケータイが発熱で機能停止するとか、
            カクカクして使い物にならないとかのニュースも珍しくないよね。

            親コメント
            • by Anonymous Coward

              > メモリ: たっぷりあります
              > CPU: ちょっとやそっとではフルパワーになりません
              なんて特殊条件はPCの世界だけですな。そんな連中をモバイル開発に回すんだからそりゃ阿鼻叫喚にもなるわ

  • by Anonymous Coward on 2011年12月20日 15時48分 (#2069105)

    配列のインデックスとデータを対応付ける #define

    ここは enum を使う方が好みだ。ずらずらとMagic Numberを連ねずにすむ。
    Cのenumがtype safe enumでないのがちとばかり残念だが。
    union int data[weather_data_e.EOI]とでもしないかぎり使わないし主題でもないが。

    #define WEATHER_DATA_SIZE (100)
    weather_data_t *weather_data = malloc(sizeof(weather_data_t)*WEATHER_DATA_SIZE);
    固定の要素数を確保するよりこっちの方が好みだ。reallocの余地が残る。
    これも主題と関係ないな。

    • #define WEATHER_DATA_SIZE (100)

      malloc の引数に使うのであれば、

      const int WEATHER_DATA_SIZE = 100;

      の方がスコープがはっきりしてよい気がします。#define の副作用もありませんし。

      一見、余計なメモリを食いそうに見えますが、どうせ最適化の過程で sizeof( weather_data_t )*WEATHER_DATA_SIZE 全体が定数展開されて消えてしまいますから。
      # というか enum があるなら const もあるだろう。const がないコンパイラなら enum も無かろう。

      --
      fjの教祖様
      親コメント
      • by tenokida (42811) on 2011年12月21日 23時33分 (#2069980) 日記

        固定の要素数を確保するよりこっちの方が好みだ。reallocの余地が残る。

        意味づけが外れてしまうから、reallocを視野に入れているなら、変数で持つべきでは?
        #allocしたサイズが隠蔽されてますから。
        #javaで、buffer.lengthがあるのはこの辺りが不便だからか?

        const int WEATHER_DATA_SIZE = 100;

        スコープstaticもしくはautomaticでないと、外部参照の可能性があるから変数が確保されるはず。
        ローカルスコープであっても、.cdata 経由で、.text セグメントに確保されるのでは。
        #gcc3(4でないのは対象環境のせい) ではこうなってました。

        親コメント
typodupeerror

※ただしPHPを除く -- あるAdmin

読み込み中...