質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

4回答

18365閲覧

構造体のアライメント

strike1217

総合スコア651

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

1クリップ

投稿2017/05/09 07:27

編集2017/05/09 14:34

C言語の本にこうありました。

バイナリファイルに対してのメモリの内容をそのまま読み書きするときは、構造体のメンバの並びや境界調整に気をつける

おそらく、境界調整による詰め物を考慮せよということなのかな?
直感的に詰め物がない構造体の方が良いと思われます。
間違っていたらご指摘をお願いします。

というわけで、実際に作ってみました。

C

1struct test { 2 char tt; 3 int hj; 4 char gh; 5 char bn; 6 //double nn; 7 };

offsetof()によって各メンバ変数を確認しました。

char tt : 4byte
int hj : 4byte
char gh : 1byte
char bn : 3byte

☆double 型のコメントを外すと、
char tt : 4byte
int hj : 4byte
char gh : 1byte
char bn : 7byte
double nn : 8byte

規則性がよくわからないのですが・・・
double型なしの時は、gh+bnで4btyeとし、4の倍数として、境界調整されている感じです。
double型ありの時は、gh+bnで8byte, tt+hjで8byteなので、8の倍数で境界調整が行われている・・・のかな・・・

メンバ変数の宣言の順番によっても詰め物のバイト数が変わると予想しているのですが、
#これを、詰め物がない理想的な構造体にするには、どうすれば良いのでしょうか??

メンバ変数の中で最も大きな変数に境界調整は合わせられるという理解で正しいでしょうか??

構造体を作るときにいちいちoffsetofマクロでアライメントを確認するのが、非常に大変な作業なので、構造体を作るときにメンバ宣言のルールのようなものがありましたら、教えてください。

一応windows 64bitで実験しました。

[追記]

Linux 64 bit でもっとメンバ変数を多くして実験してみたところ、
double, float, int, short, unsigned char, char
の順に下から大きい順に並べて構造体を作成してみたところ、gccコンパイラでのoffsetofの結果は、詰め物バイト数を最小にすることができました。
大きいバイト数から数えて(下から)、最大のバイト数の倍数(この場合、double型の8の倍数)になるように構造体の各メンバ変数を作成、調整をしていけば良さそうです。

どうしても、詰め物を入れざるを得ない状況(メンバ変数の全バイトが倍数にならない)で、詰め物を0byteにするのは実質、不可能なんでしょうか?

例えば、詰め物を0byteに出来るように、あえて使わないメンバ変数を作成し、0で初期化した場合、それをバイナリファイルへの出力としたら、中間に余計な0が入ってしまいます。
これでは、ダミー変数を用意しても詰め物と同じ結果になりませんか?

[追記2]

メンバの順序

いろいろ出てきますが、詰め物の最小化とメンバ変数の順序には関係性がありそうです。
maismakunさんやケイロニアンさんの回答をもとにすると、CPUとメモリ、メモリと記憶装置、の両方とも理想的な構造体を作成するのは、難しいと思われます

64bit CPUに話を絞るのですが、8^2なので、実行単位は8byteだと思います。
構造体全体のサイズを8の倍数にし、さらに詰め物を最小にするような構造体の作成はやや大変だと思います。
メンバ変数の順序以外に理想形に近づける方法は他にありますでしょうか?

kazuyakazuya👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

詰め物がない理想的な構造体

人間にとっては理想的かもしれませんが、CPUにとっては全くそうではありません。

データのずれに寛容なx86ですら、4バイト境界に合っていない32ビット整数の読み込みには境界を守ったデータより時間がかかりますし、x64のSSE系の命令や、RISCのCPUでは境界が合わないと例外を投げてしまいます。メモリ1バイトが惜しいような時代ではないのですし、CPUもコンパイラも速度優先で設計されているのです。

データ型のアラインメントとは何か,なぜ必要なのか?

投稿2017/05/09 07:53

編集2017/05/09 07:57
maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2017/05/09 08:02

そして、「構造体」というのはC言語が見せているものに過ぎず、CPUは単体のデータを処理するだけですので、「構造体のアライメント」は、「構造体メンバの中でいちばアライメントが大きなもの」に合わせられます。
strike1217

2017/05/09 09:36

CPUにとっては全くそうではありません。 ほ? そうなんですか?
maisumakun

2017/05/09 09:42

はい、下に書いたように、よくて「揃っていないと処理が遅くなる」、最悪「揃っていないと処理不能」なのです。
strike1217

2017/05/09 09:58 編集

質問の最初の方に書いたのですが、「バイナリファイルに対してのメモリの内容をそのまま読み書きするときは、構造体のメンバの並びや境界調整に気をつける 」 というのは、詰め物を最小にするば、良いというものではないということでしょうか?
strike1217

2017/05/09 10:11

詰め物がない構造体 がCPUにとって理想的なものでないとすると、どのような構造体というものがCPUにとっての理想形なのでしょうか? あ、すいません。 CPUにも種類がたくさんありますので、AMD64やx86アーキテクチャに絞ってもらえると助かります。
tacsheaven

2017/05/09 10:19

Intel の公式資料 http://www.intel.co.jp/content/dam/www/public/ijkk/jp/ja/documents/developer/248966-024JA.pdf の 3.6.4 に詳しいですが、 ・キャッシュ境界の 64byte ・SIMD 境界の 16byte ・浮動小数点境界の 8byte ・整数境界の 4byte などがあります。 キャッシュ境界は、構造体全体のサイズが 64 の倍数になるようにパディングしないと、速度面でペナルティが発生する、ということです(64に複数個収められるサイズ、すなわち 32byte などもよい状態) これは CPU とメモリとのやり取りを最適化しているのであって、メモリと外部記憶(ファイル)との最適化ではありません。バイナリファイルのやり取りは、構造体のメモリイメージをそのまま書くとパディング分が無駄になります(ので、構造体のメンバごとに書くなど対策が必要)
strike1217

2017/05/09 10:41

tacsheavenさん、「バイナリファイルのやり取りは、構造体のメモリイメージをそのまま書くとパディング分が無駄になります」 ほおお!なるほどです。 バイナリファイルに余分な物(意図して入れたものではないもの)が入ってしまうということですね!
strike1217

2017/05/09 10:49

アライメントというのは、CPUとメモリ間とメモリと外部記憶の間、両方に登場するんですね!
maisumakun

2017/05/09 10:51

外部記憶の方は、面倒だからと「構造体のメモリイメージをそのまま書き出す」とアライメントの影響を受けることになります。 きっちり1データごとにハンドリングすれば、隙間なく詰めてもまったく問題ありません。
strike1217

2017/05/09 11:04

外部記憶に対しては、詰め物がない構造体が理想という事ですよね? CPUに対しては、詰め物がない構造体が理想形だとは限らないということですね!!
strike1217

2017/05/09 11:19

CPUにとっての理想の構造体・・・ 自分なりに考えたのですが、構造体のサイズが2のn乗で理想形になるのでしょうか・・・ 32や64といったサイズがCPUにとっての理想形になりそうですが・・・
tacsheaven

2017/05/10 01:06

CPUにとっての理想形はキャッシュ(L1キャッシュ)のサイズの倍数(1/2などもあり)ですね。これが最も効率よく CPU がアクセスできるものとなります。 あと、同じアーキテクチャでしか使わないならいいのですが、クロスプラットフォームを考慮した場合、環境依存する構造体メモリイメージの直接のやり取りは危険です。エンディアンの違い、パディングの違いといったものにより、意図しないデータになってしまいます。
yohhoy

2017/05/11 07:40 編集

tacsheavenさんが意図しているのは「キャッシュライン(cache-line)」かもしれませんね。L1キャッシュ全体のサイズはあまり関係ないと思います。 http://news.mynavi.jp/column/architecture/006/ CPUはこの「キャッシュライン」単位でメインメモリとL1キャッシュのやり取りをしますので、特にマルチコアプロセッサで高い動作性能を出すには、この単位を意識することが重要になります。 (注:この話はプログラム実行中のメモリとCPU間の話であり、元の主題「ファイルI/Oと構造体パディング」には直接関係しません)
strike1217

2017/05/11 12:15

キャッシュラインは、使用しているCPUごとに異なるものなんですかね? 調べてみます。
guest

0

直感的に詰め物がない構造体の方が良いと思われます。

何をもって「良い」とするかは意見が分かれますね。コンパイラーは無意味なことはしません。パディング(詰め物)は必要だから入れているのです。他の方も書かれている通り、ターゲットのCPUで最もパフォーマンスが引き出せるように調整してくれているのです(必ずしもプログラマーの意図通りではないかもしれませんが)。プログラムの中だけでしか使わない構造体なら、極端に搭載メモリが少ないマシンでもない限りそれが問題になることはほとんどないので、基本的にはコンパイラーに任せるのが一番良い方法です。

ところがバイナリファイルとなると話は違ってきます。ファイルに出力するということは、別のマシンとのデータのやりとりに使うかもしれません。構造体のパディングは処理系依存なので、同じヘッダーファイルをインクルードしてコンパイルしても異なる配置になる可能性があります。
そうなると、あるマシンで出力したバイナリファイルを別のマシンで読ませるとまったくでたらめな内容になってしまうということが起こり得ます。あるいは、まったく同じプログラムを32bit版と64bit版のようにコンパイルを分けただけで、同じマシンで動作するにもかかわらずバイナリファイルの内容が異なるため互換性がなくなる、といった事態も起こり得ます。

バイナリファイルに対してのメモリの内容をそのまま読み書きするときは、構造体のメンバの並びや境界調整に気をつける

これの意図することはそういうことです。

理想を追い求めていらっしゃるようですが、何が理想なのかは状況に応じて変わってきます。コンパイラーは「CPUにとって理想的な配置」になるように自動的にパディングを入れています。今時の64bit向けコンパイラーなら、メンバ変数にdoubleなどの8バイトのデータ型があれば、構造体全体のサイズも自動で8の倍数になります(してくれます)。なるべくパディングが入らないように気をつけることは有意義ですが、やりすぎて関連する情報があちこちに散らばったりして読みづらくなったら、それはそれで「理想」とはほど遠い気がします。マシンサイクル単位でチューニングしなければならないような特殊な状況でもない限りは、利用する立場で考えて読みやすい構造体を設計することが、結果的には理想に近づくのだろうと思います。ファイルや通信などでのデータのやりとりでは、今のトレンドはXMLやJSONなどのテキスト形式ですから、**『バイナリファイルを使うことは構造体の理想以前にプログラムの仕様としてどうなの?』**という話になってしまいます。

投稿2017/05/10 02:33

catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/05/10 05:57

ふむーーー。 なるほどです。
strike1217

2017/05/11 14:40

1つだけよろしいですか? バイナリファイルへの読み出し、書き込みに構造体を使用するのはもう時代遅れなのでしょうか??
catsforepaw

2017/05/11 14:59

目的にもよります。 例えば、別アプリや他システムとのやりとりはせずに内部処理のためだけの一時ファイルのような使い方なら、バイナリファイルとして構造体(メモリイメージ)をそのまま読み書きすることに問題はありませんし、パフォーマンスの観点からあえてそうすることもあり得ます。
strike1217

2017/05/11 15:23

そうですか! こちらも場合によるわけですね!
guest

0

これを、詰め物がない理想的な構造体にするには、どうすれば良いのでしょうか??

コンパイラによりますがVC系なら#pragma pack(サイズ)によりパックサイズを制御できます。
以下、(1)の場合はパディングなしになります。

C++

1# pragma pack (1) 2struct x_ 3{ 4 char a; // 1 byte 5 int b; // 4 bytes 6 short c; // 2 bytes 7} MyStruct; 8# pragma pack ()

参考:構造体のパッキングに関連する処理

バイナリファイルによる一括入出力したいデータ(構造体)では、安直かもしれませんが、うちでもいまだにわりと普通に利用しています。

投稿2017/05/09 11:21

can110

総合スコア38262

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/05/09 11:30

おお! 試してみます。 これは、「境界調整なし」ということなんですか?
can110

2017/05/09 11:33

そうですね。「境界調整なし」=隙間なしです。
strike1217

2017/05/09 11:34

おお!! 出来ました。 Linuxの場合(gcc)はどうやるんですか?
can110

2017/05/09 11:35

すみません。gccの場合は分かりません。
strike1217

2017/05/09 11:36

そうですか! わかりました。
can110

2017/05/10 02:41

gccでも使えるのですね。情報ありがとうございます。
guest

0

こんにちは。

これを、詰め物がない理想的な構造体にするには、どうすれば良いのでしょうか??

順序を変えればOKです。(ただし、最適な順序は処理系によって異なるかも知れません。)

C++

1struct test { 2 char tt; 3 char gh; 4 char bn; 5 int hj; 6 double nn; 7 };

これでpaddingはbnの次に1バイト確保されるだけの処理系が多いです。

CPUはデータをアクセスする時「データバス幅」単位でアクセスします。データバス幅はざっくり1サイクルでアクセスできるデータの最大ビット数と考えても大きくは外れません。(昨今の複雑なCPUではそんなに単純な話ではないですが。)

例えば、PCの場合、int型が4バイトでデータバス幅が32ビットあり、データは32ビット単位きれいに並んでメモリに置かれています。その32ビット区切りにきれいに入っている場合は1サイクルでアクセスできます。そうでない場合は跨っている2つの32ビット区切りをそれぞれアクセスする必要があるので遅くなります。

で、double型ですが、これは通常64ビットあります。昔の32ビットバス幅のCPUなら32ビット境界に配置していれば最速だったのですが、最近のCPUはdoubleは64ビット境界に配置した方が高速です。(詳しい理由は私も把握していません。PCのデータバスはシリアルなので複雑なのです。)

以上をまとめると、int型は32ビット(4バイト)境界、double型は64ビット(8バイト)境界に配置するのが最も高速です。
そして、コンパイラはデフォルトではデータをその境界に配置します。もちろん、構造体先頭も適切な境界へ配置されます。

確かgccはスタックを16バイト単位に調整してから、ローカル変数を配置していたと思います。それはこの境界に配置することで高速アクセスできるようにするためです。
確かgccのlong doubleは16バイト境界だったはずなのでそれを考慮しているのではないかと思います。

投稿2017/05/09 09:10

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/05/09 09:55

構造体の場合、メンバ変数のバイト数が小さい順に上から並べれば、必ず詰め物のサイズは最小に抑えられるのでしょうか?
Chironian

2017/05/09 10:03

そう言う視点で考えたことは無かったのですが、なんとなくそうなりそうな気はします。 でも、アライメントは処理系依存なので、「常に」そうであることを証明するのはかなり難しいと思いますし、そもそも正しくないかも知れません。
strike1217

2017/05/09 10:07

ああ~~ アーキテクチャやCPUの話が混ざると分かりにくくなるんですよね・・・ 「なんとなくそうなりそうな気はします。」 そうですか! 分かりました。
strike1217

2017/05/09 10:39

「メンバ変数の中で最も大きなバイト数の変数に境界調整は合わせられる」としたら、構造体を作るときに詰め物を最小にすることはできそうですが・・・ この理解は正しいのでしょうか?
cateye

2017/05/09 10:58 編集

clang++はパディングあると” warning: padding struct 'Percentage' with 4 bytes to align 'Sum' [-Wpadded]”などというワーニング投げてきます@@;・・・intの次にdoubleがある構造体ですd^^ この場合、intを2個用意して(1つはダミー)doubleの前におくとワーニングが消えます。
strike1217

2017/05/09 11:02

そうなんですか! windows のcl オプションなしだと出てこないですね・・・ コンパイラによっても違うという事なんですかね・・・
strike1217

2017/05/09 11:12

「これでpaddingはbnの次に1バイト確保されるだけの処理系が多いです。」 すいません。少し気になったのですが、 この場合、tt+gh = 8byte, bn+hj = 8byte, nn = 8byteだと思ったのですが、 bnの方には、余分に3byteの詰め物が入るのではないのですか? どうして、1byteなのでしょうか?
strike1217

2017/05/09 11:15

あ、そうかごめんなさいいい char型なので、違いますね
strike1217

2017/05/09 11:17

tt+gh+bn = 4byte, hj = 4byte, nn = 8byte だから 詰め物が1byteなのですね! 失礼しました。
cateye

2017/05/09 11:18

私の場合は、(コンパイルオプションで)ワーニングをできる限り出すようにしているのでそのせいだと思います。通常の32・64ビット処理系ならできるだけ8バイト境界にしておけば、パディングについてはそれほど考えなくてもいいと思います。・・・考え出すとキャッシュのラインサイズまで考えなくちゃならない;;・・・もとが「バイナリファイルの読み書き」の話なのでプログラムAが出力したデータをBプログラムで読んだりする場合、プログラムを作った処理系が違えば構造体の構成も変わるから?・・・という事じゃないのでしょうか?
strike1217

2017/05/09 11:26

intの次にdoubleがある構造体では、普通にint型のメンバに4byteの詰め物を入れれば、良いのではないんですかね? なぜwarningになるのかが分からないんですが・・・
cateye

2017/05/11 12:34

私も(-Weverythingをコンパイラオプションにした時)このワーニングに初めて出くわした時は、ゲ?!とか思ったんですが、めんどくさいので無効にしないで使っています。最近はメモリ節約(^^;のヒント程度に考えています。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問