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 を
もう一つ (スコア:2)
TMPR[100];
HMDT[100];
ATMS[100];
:
年寄りだと、こういう感じにしそう。
さらに一つ (スコア:1)
int tmp000, tmp001, tmp002, .. tmp099;
int hum000, hum001, hum002, .. hum099;
...
え? 特定のデータにアクセスするのが難しいって?
何のためにポインタ変数があるんだ!
// 途中で、間違い探し的に変数名が変わるのは仕様
//// 10個の変数ずつ定義した行をコピペして、10の位だけ書き換えれば
//// 変数名間違えにくいし、効率的だね!
Re:もう一つ (スコア:1)
データの扱い方によっては…具体的に言うと、『1回分のデータ』をセットで扱うことが無く、むしろ気温は気温だけで、湿度は湿度だけでといった具合にデータの種類毎に扱うような場合はむしろそちらの方が適切ということも有り得ますね。
『適切なデータ構造』というのは、そのデータがどのように扱われるのかにも依存するということで。
それはどういう環境でそのコードを動かしたいのか依存 (スコア:1)
あまりメモリサイズを気にしなくてよいとか、メモリなんて腐るほどあるわいとか、そういう世界では構造体の配列を使うのが正解。
ところが、「メモリの1bitは血の一滴」などという世界では構造体は怖くて使えない。特に今回は構造体の要素が5個…つまり 2^n の数ではない個数存在する。
メモリがキツキツである事をしらないコンパイラは、alignment をきれいにするために:
を
であるかのように確保してくれる(もちろん、本当は padding1, padding2, padding3 へはアクセスできない)。
コンパイラに「メモリキツキツだからっ、無駄遣いしないでっっ」と指示できるコンパイラならいいのだが、指示できないコンパイラ(あるいは指示できるのだが、コンパイラの我が強くて言う事を聞かない場合)は、[100][5] の配列を作るしかないのである。
fjの教祖様
Re:それはどういう環境でそのコードを動かしたいのか依存 (スコア:2)
そういう世界で使うコンパイラは、境界合せをするのだろうか。
Re: (スコア:0)
そういう世界で使うコンパイラは、境界合せをするのだろうか。
word-aligneという意味なら、普通は境界合わせしますよ。
構造体のメンバにintが居ればintに, long longが居ればlonglongにというように。
詰める必要があるなら、 __attribute__((packed))とか __packedとかで修飾。問題は標準がなくコンパイラに合わせる必要があること。
は、この世界では使い物にならないでしょう。
Re:それはどういう環境でそのコードを動かしたいのか依存 (スコア:1)
それが必ずしもその一言で消失してくれない辺りが鬱陶しい。
「ポインタ動かす所は別にすればいいじゃん」
攻撃はよくある。もちろん、山盛りのバグ付きで。
最も頭痛がするのは、通信ライブラリ周りで、little endian を big endian に直すのに構造体に値を入れる過程で htonl(3)とかを呼べばいい、と思い込んでいるコードが絡んでくる場合で…padding だけでも結構頭痛が…
# だれか教えてくれ。Cの構造体のエレメントのメモリ展開時の順序は本当に保証されているのか??
fjの教祖様
Re:それはどういう環境でそのコードを動かしたいのか依存 (スコア:2)
どういう場面?
書き方によってはバッファから読むところで例外が出る事はあるけど、(int境界に合わせてある普通の)構造体特有の問題はあるのかな。
Re:それはどういう環境でそのコードを動かしたいのか依存 (スコア:1)
プロトコルヘッダーをバイト列に変換したり、逆にバイト列をプロトコルヘッダーの構造体に直す所。
ソケット周りの実装を読むと、途中で構造体へのポインタがバイト列へのポインタにすり変わったり、その逆が起こったりしているものが多い。
大昔のコードが padding とかを一切しなかったので、ポインタのすり替えでできたのだが、その「伝統」が受け継がれてしまっている。
fjの教祖様
Re: (スコア:0)
> # だれか教えてくれ。Cの構造体のエレメントのメモリ展開時の順序は本当に保証されているのか??
http://portable-c.jugem.jp/?eid=17 [jugem.jp]
JIS X 3010:2003を実際に読んで裏をとるべきだが、とりあえずググッた結果を鵜呑みにする限りでは保証されているらしい。
# コンパイラが規格準拠でない可能性まで考慮してるなら知らんとしか
Re: (スコア:0)
あるデータ型に対して、効率よくアクセスできる(ものによっては Bus error を起こさない)ように、変数を詰め込むだけだから、この場合は padding は入らないと思うけどなぁ。
そりゃぁ、2^n に align すれば効率は良いだろうけど、それはかなり過剰だと思う。
Re:それはどういう環境でそのコードを動かしたいのか依存 (スコア:1)
Cache access align を考慮して、最近は padding が入るコンパイラが多い。cache alignment にぴったり合っていないと IO が 2line 分のIOを消費するから。逆にいうと、ここに padding があってもなくても、Bus IO 的には同じ速度。
逆に今時 Bus error を cache line 単位で考えていないという事は、昔苦労したか、現在組み込みで苦労している事が偲ばれる。
fjの教祖様
"int weather_data[5 * 100];" でもかまわない。 (スコア:0)
データ構造の詳細はどうでも良いことで、重要なことはインターフェースを明かにすること。
インプリメントを行う *.c ファイルには、
static int weather_data[5 * 100];
であっても良い(センスを感じられないのは確かだが)。
でも、static は外せない。詳細はどうでも良いことで、外部に見せる必要がないから。
インターフェイスを宣言する *.h には、
- データ構造の初期化関数
- 後始末関数
- 値を設定する関数
- 必要に応じてデータ構造から値を取得する関数
- データ構造を使って計算する関数
のプロトタイプ宣言だけを書いておけば良い。
Re:"int weather_data[5 * 100];" でもかまわない。 (スコア:1)
グローバル変数は static にして外部へは公開せず、インターフェース関数を公開するというのは確かにその通りですが、今回の日記ではそこが主眼ではないので static は付けませんでした。
それよりも『データ構造の詳細はどうでも良いこと』という主張には疑問です。
単に正しく動作させるというだけなら配列だろうが構造体だろうが問題となることはありませんが、保守性や流用性を考えたとき、適切なデータ構造を選択するということは重要になってくると思います。
実際、データ構造が適切でないために、それらのデータを扱う処理の記述が複雑になり、取り除くことの難しいバグの温床となっているソースコードは今までに数多く見てきました。
今回のように単純な例ならバグに繋がることは少ないと思いますが、常に適切なデータ構造を選択するよう心掛けることが、より複雑なデータを扱う際にバグを減らすことに繋がると考えます。
Re: (スコア:0)
static キタ───(゚∀゚)───!!
それは一番やっちゃいけない実装。
バッファサイズを100回分に限るのもいけない。
スタックサイズがあまりに小さくヒープを切るにも制限が多い等、合理的な理由があるなら話は別だが。
いいかげん観念してオブジェクト指向しちゃいなyo!!
Re:"int weather_data[5 * 100];" でもかまわない。 (スコア:1)
オブジェクト指向を意識した実装に見えるが。
1を聞いて0を知れ!
Re: (スコア:0)
複数オブジェクトを作ったら気づかない間に相互干渉する仕様がオブジェクト指向的なんですか。オブジェクト指向ばんざい。シングルトンにするならいいかもしれないけどそんな前提条件どこにも書かれてないし。
Re:"int weather_data[5 * 100];" でもかまわない。 (スコア:1)
> データ構造の詳細はどうでも良いことで、重要なことはインターフェースを明かにすること。
って書いてあるだろ。
グローバルにすべきかどうかは自分で考えりゃいいし、気づかない間に相互干渉するのであればインターフェースが明かとは言い難いわな。
1を聞いて0を知れ!
Re: (スコア:0)
じゃあインスタンスを同時に使えるかどうかというインターフェースをヘッダファイルで明らかにできないC言語に欠陥があるってことだな。
# コメントで人間向けに書いておけばいいとかいうなよ。
Re:"int weather_data[5 * 100];" でもかまわない。 (スコア:1)
> # コメントで人間向けに書いておけばいいとかいうなよ。
言うに決まってるだろ。
読まない馬鹿が使うと困るって言いたいのなら #2068967 が挙げている「データ構造の初期化関数」を利用して複数オブジェクトを作ることを阻止するってことくらい、プログラマなら誰でも思いつくよな。
それに、欠陥つーのも違うだろ。そもそもC言語がオブジェクト指向に向いていないだけ。
1を聞いて0を知れ!
Re: (スコア:0)
元の人はJavaのstaticとC言語のstaticを混同してるんじゃないの?
もちろんJavaの場合でもstaticを多用するのはあんましよくない。
ちなみにC言語の高速化テクニックの一つとしては
int w[500]
的なのはありました。
シーケンシャルに順番にアクセスして処理できる場合はそっちの方が早くなるから。
メモリアクセスの最適化の問題なので、そういう書き方をした方が今でも早くなりそうだけど、
汚いコードでバグが出やすくなるから、まあ、最初は普通に構造体使って書いておいて、
プロファイルしてここがボトルネックだというのなら、それから書き直しても遅くはない。
alignmentとかpaddingとか気にしなくていいのかな (スコア:0)
この場合はintだから大抵の処理系で大丈夫だろうけし、パフォーマンスやメモリ使用量を気にする用途かそうでないかにもよるだろうけど。
Re:alignmentとかpaddingとか気にしなくていいのかな (スコア:1)
もちろんパフォーマンスやメモリ使用量の制約がある場合は、それに応じた形にする必要がありますが、それはあくまでも『特殊な条件』という意識が必要なのではないかと思います。
一般的にはやはりバグを生まないことが重要だと思い、そのために読みやすさ、間違いの犯しにくさを実現する方法を考えています。
Re:alignmentとかpaddingとか気にしなくていいのかな (スコア:1)
その条件だと「Cを使うな」の一択に…
メモリ: たっぷりあります
CPU: ちょっとやそっとではフルパワーになりません
などという特殊条件用の言語ではありませんから、Cは。
fjの教祖様
Re:alignmentとかpaddingとか気にしなくていいのかな (スコア:1)
C言語が使われるのがほぼパフォーマンスやメモリ使用量の制限の厳しい条件下だとしても、コードの全てにそのような条件でのチューニングが必要とは限らない、むしろチューニングが必要なのは一部のコードのみとなるケースがほとんどなのではないでしょうか。
例えば処理速度なら、『プログラムの処理にかかる時間の80%はコード全体の20%の部分が占める』 [wikipedia.org]とすれば、処理速度の要求がある程度厳しい場合でも、コード全体のうちおよそ 80% についてはチューニングは不要であることが多いと思われます。
チューニングが必要な部分と不要な部分で別の言語を使うべきとまでは言わないですよね?
Re:alignmentとかpaddingとか気にしなくていいのかな (スコア:1)
それすら「規模如何」。
Emacsという例もある。
fjの教祖様
Re: (スコア:0)
Firefoxもそうですな(チューニングが今なお必要な部分はC++、それ以外はJavaScript)。
速いことで有名なChromeのJavaScriptエンジンのV8ですら一部の実装はJavaScriptで行われている。
Re: (スコア:0)
むしろ、そういう条件がそう特殊でもなくなってきてるのに何故か皆
「C/C++使いじゃないとホンモノのプログラマじゃない」
みたいなマッチョイズムに囚われてて使わなくても良い所にC/C++使って不幸になってる事が多いような…
Re:alignmentとかpaddingとか気にしなくていいのかな (スコア:1)
>むしろ、そういう条件がそう特殊でもなくなってきてるのに
そうとも言い切れないような。
今日も
>>メモリ: たっぷりあります
>>CPU: ちょっとやそっとではフルパワーになりません
と思ってる人間が書いた糞遅いコードを修正したいけどできない場面に遭遇してきたところです。
たしかに数倍くらいなら遅くてもなんとかなる場合も多いけど、
数百倍、数千倍、数万倍遅いコードを書かれても平気なほど、
メモリもCPUパワーもバッテリー容量も潤沢なわけではないぞ。
そういえば、冬場なのにどこかのケータイが発熱で機能停止するとか、
カクカクして使い物にならないとかのニュースも珍しくないよね。
Re: (スコア:0)
> メモリ: たっぷりあります
> CPU: ちょっとやそっとではフルパワーになりません
なんて特殊条件はPCの世界だけですな。そんな連中をモバイル開発に回すんだからそりゃ阿鼻叫喚にもなるわ
好みで言うと (スコア:0)
ここは 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の余地が残る。
これも主題と関係ないな。
Re:好みで言うと (スコア:1)
malloc の引数に使うのであれば、
の方がスコープがはっきりしてよい気がします。#define の副作用もありませんし。
一見、余計なメモリを食いそうに見えますが、どうせ最適化の過程で sizeof( weather_data_t )*WEATHER_DATA_SIZE 全体が定数展開されて消えてしまいますから。
# というか enum があるなら const もあるだろう。const がないコンパイラなら enum も無かろう。
fjの教祖様
Re:好みで言うと (スコア:1)
意味づけが外れてしまうから、reallocを視野に入れているなら、変数で持つべきでは?
#allocしたサイズが隠蔽されてますから。
#javaで、buffer.lengthがあるのはこの辺りが不便だからか?
スコープstaticもしくはautomaticでないと、外部参照の可能性があるから変数が確保されるはず。
ローカルスコープであっても、.cdata 経由で、.text セグメントに確保されるのでは。
#gcc3(4でないのは対象環境のせい) ではこうなってました。