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

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

ただいまの
回答率

90.51%

  • C

    4509questions

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

  • C++

    4414questions

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

構造体のアライメント

受付中

回答 4

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 5,556

strike1217

score 568

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

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

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

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

struct test {
        char tt;
        int hj;
        char gh;
        char bn;
        //double nn;
    };


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の倍数にし、さらに詰め物を最小にするような構造体の作成はやや大変だと思います。
メンバ変数の順序以外に理想形に近づける方法は他にありますでしょうか?

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+8

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

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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/09 17:02

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

    キャンセル

  • 2017/05/09 18:36

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

    キャンセル

  • 2017/05/09 18:42

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

    キャンセル

  • 2017/05/09 18:58 編集

    質問の最初の方に書いたのですが、「バイナリファイルに対してのメモリの内容をそのまま読み書きするときは、構造体のメンバの並びや境界調整に気をつける 」

    というのは、詰め物を最小にするば、良いというものではないということでしょうか?

    キャンセル

  • 2017/05/09 19:11

    詰め物がない構造体 がCPUにとって理想的なものでないとすると、どのような構造体というものがCPUにとっての理想形なのでしょうか?

    あ、すいません。
    CPUにも種類がたくさんありますので、AMD64やx86アーキテクチャに絞ってもらえると助かります。

    キャンセル

  • 2017/05/09 19: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 とメモリとのやり取りを最適化しているのであって、メモリと外部記憶(ファイル)との最適化ではありません。バイナリファイルのやり取りは、構造体のメモリイメージをそのまま書くとパディング分が無駄になります(ので、構造体のメンバごとに書くなど対策が必要)

    キャンセル

  • 2017/05/09 19:41

    tacsheavenさん、「バイナリファイルのやり取りは、構造体のメモリイメージをそのまま書くとパディング分が無駄になります」

    ほおお!なるほどです。
    バイナリファイルに余分な物(意図して入れたものではないもの)が入ってしまうということですね!

    キャンセル

  • 2017/05/09 19:49

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

    キャンセル

  • 2017/05/09 19:51

    外部記憶の方は、面倒だからと「構造体のメモリイメージをそのまま書き出す」とアライメントの影響を受けることになります。

    きっちり1データごとにハンドリングすれば、隙間なく詰めてもまったく問題ありません。

    キャンセル

  • 2017/05/09 20:04

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

    キャンセル

  • 2017/05/09 20:19

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

    32や64といったサイズがCPUにとっての理想形になりそうですが・・・

    キャンセル

  • 2017/05/10 10:06

    CPUにとっての理想形はキャッシュ(L1キャッシュ)のサイズの倍数(1/2などもあり)ですね。これが最も効率よく CPU がアクセスできるものとなります。

    あと、同じアーキテクチャでしか使わないならいいのですが、クロスプラットフォームを考慮した場合、環境依存する構造体メモリイメージの直接のやり取りは危険です。エンディアンの違い、パディングの違いといったものにより、意図しないデータになってしまいます。

    キャンセル

  • 2017/05/11 16:37 編集

    tacsheavenさんが意図しているのは「キャッシュライン(cache-line)」かもしれませんね。L1キャッシュ全体のサイズはあまり関係ないと思います。

    http://news.mynavi.jp/column/architecture/006/

    CPUはこの「キャッシュライン」単位でメインメモリとL1キャッシュのやり取りをしますので、特にマルチコアプロセッサで高い動作性能を出すには、この単位を意識することが重要になります。

    (注:この話はプログラム実行中のメモリとCPU間の話であり、元の主題「ファイルI/Oと構造体パディング」には直接関係しません)

    キャンセル

  • 2017/05/11 21:15

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

    キャンセル

+5

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

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/10 14:57

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

    キャンセル

  • 2017/05/11 23:40

    1つだけよろしいですか?

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

    キャンセル

  • 2017/05/11 23:59

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

    キャンセル

  • 2017/05/12 00:23

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

    キャンセル

+3

こんにちは。

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

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

struct test {
        char tt;
        char gh;
        char bn;
        int hj;
        double nn;
    };

これで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 18:55

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

    キャンセル

  • 2017/05/09 19:03

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

    キャンセル

  • 2017/05/09 19:07

    ああ~~
    アーキテクチャやCPUの話が混ざると分かりにくくなるんですよね・・・

    「なんとなくそうなりそうな気はします。」
    そうですか!
    分かりました。

    キャンセル

  • 2017/05/09 19:35 編集

    うろ覚えなので引用元を特定できないのですが「構造体のメンバーの配置が宣言した通りになると思うのは間違い」というのがあったと思います。←確か処理系依存?・・・確認しました(http://portable-c.jugem.jp/?eid=17)が私の間違いですmm

    キャンセル

  • 2017/05/09 19:39

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

    この理解は正しいのでしょうか?

    キャンセル

  • 2017/05/09 19:54 編集

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

    キャンセル

  • 2017/05/09 20:02

    そうなんですか!
    windows のcl オプションなしだと出てこないですね・・・

    コンパイラによっても違うという事なんですかね・・・

    キャンセル

  • 2017/05/09 20:12

    「これでpaddingはbnの次に1バイト確保されるだけの処理系が多いです。」
    すいません。少し気になったのですが、

    この場合、tt+gh = 8byte, bn+hj = 8byte, nn = 8byteだと思ったのですが、
    bnの方には、余分に3byteの詰め物が入るのではないのですか?

    どうして、1byteなのでしょうか?

    キャンセル

  • 2017/05/09 20:15

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

    キャンセル

  • 2017/05/09 20:17

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

    キャンセル

  • 2017/05/09 20:18

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

    キャンセル

  • 2017/05/09 20:26

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

    キャンセル

  • 2017/05/11 21:34

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

    キャンセル

+3

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

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

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


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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/09 20:30

    おお!
    試してみます。

    これは、「境界調整なし」ということなんですか?

    キャンセル

  • 2017/05/09 20:33

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

    キャンセル

  • 2017/05/09 20:34

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

    キャンセル

  • 2017/05/09 20:35

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

    キャンセル

  • 2017/05/09 20:36

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

    キャンセル

  • 2017/05/10 11:01

    gccでも同じpragmaを使えます(VCとの互換性のため)
    https://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Structure_002dPacking-Pragmas.html

    キャンセル

  • 2017/05/10 11:41

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

    キャンセル

同じタグがついた質問を見る

  • C

    4509questions

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

  • C++

    4414questions

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