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

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

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

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

Q&A

解決済

6回答

1245閲覧

メンバが同じなら構造体のメモリレイアウトは同じか?

SaitoAtsushi

総合スコア5437

C

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

0グッド

3クリップ

投稿2018/09/21 19:34

前提

C99 を想定しています。

この質問は具体的な状況があるわけではなく、言語仕様に対する疑問です。

質問

メンバの内容が同じ構造体の宣言をふたつしたとき、このふたつの構造体が同じレイアウトを持つことを保証するような文言は仕様にあるでしょうか?

仕様を確認したところ、構造体のメモリレイアウトについて保証されているのは

  • メンバは宣言順に並ぶ
  • 構造体を指すポインタはその構造体の先頭要素を指すポインタと互換性がある
  • メンバは境界調整済である

というような事柄は書いてありますが

  • メンバの間や構造体の最後に詰め物をしてもよい

ともあり、その詰め物 (パディング) の仕方には制約が書かれていないように見えます。

境界調整さえ出来ていれば、例えば乱数だとか名前のハッシュ値だとかで詰め物を余計に入れるなどしてもその処理系は規格には合致すると言えるでしょうか? もちろんそんなことをする合理的理由は無いので、あくまでも極端な例です。

例のソースコード

つまり、こんなコードの動作は仕様で保証されるかということです。

c

1#include <stdio.h> 2 3struct foo { 4 char a; 5 int b; 6}; 7 8struct bar { 9 char c; 10 int d; 11}; 12 13int main(void) { 14 foo foo1 = {1, 2}; 15 bar bar1 = {3, 4}; 16 17 foo* foo2 = &bar1; 18 bar* bar2 = &foo1; 19 20 printf("%d, %d\n", foo2.b, bar2.d); 21 22 return 0; 23}

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

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

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

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

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

guest

回答6

0

ベストアンサー

JIS規格を見てみましたが、「6.7.2.1 構造体指定子及び共用体指定子」に、

構造体又は共用体オブジェクトのビットフィールド以外の各メンバの境界は、その型に適した処理系定義の方法で調整する。

という記述がありますので、境界調整は型のみに依存すると読むのが自然だと思います。
ただその「型に適した処理系定義の方法」に乱数を用いた方法が駄目かと言われると、文章的には言ってませんね。

plain

1例 全ての配列メンバが同様に境界を調整されると仮定すると,次の宣言について 2 struct s { int n; double d[]; }; 3 struct ss { int n; double d[1]; }; 4 次の三つの式は, 5 sizeof (struct s) 6 offsetof(struct s, d) 7 offsetof(struct ss, d) 8 同じ値を持つ。

で、同じ値を持つのが無条件でなく、「全ての配列メンバが同様に境界を調整されると仮定すると」の条件付きなので、「全ての配列メンバが同様に境界を調整されない」という処理系を許容するように読めます。

投稿2018/09/21 23:57

otn

総合スコア84423

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

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

maisumakun

2018/09/22 00:14

実際、#pragma packのように、同じファイル内で場所ごとにアライメントを変える機能が存在するコンパイラもありますしね。 なお、「後方で宣言された構造体のメンバへのポインタは,その構造体中で前方に宣言されたメンバへのポインタと比較すると大きく」なければならないとのことで、順番を入れ替えることは許されないとのことです。
guest

0

つまり、こんなコードの動作は仕様で保証されるかということです。

自身で指摘されているように、該当コードはコンパイルエラーになりますね。またキャストで強引に型変換した場合も、エイリアシング規則違反により動作保証はないと考えます。


C99言語仕様では、特定条件をみたすときだけ 異なる構造体型同士の相互運用を許容します。§6.5.2.3 Structure and union members, Paragraph 5より:

One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the complete type of the union is visible. Two structures share a common initial sequence if corresponding members have compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.

つまり標準C99仕様の下では、下記パターンのみ動作保証がなされます。

C

1struct foo { 2 char a; 3 int b; 4}; 5 6struct bar { 7 char c; 8 int d; 9}; 10 11int main() { 12 // 共有体メンバがcommon initial sequenceを共有する構造体の時に限り 13 union { 14 struct foo f; 15 struct bar b; 16 } u; 17 18 // 構造体foo経由での書き込みを 19 u.f.a = 1; 20 u.f.b = 2; 21 22 // 構造体bar経由で読み取ることが可能 23 printf("%d, %d\n", u.b.c, u.b.d); 24}

なおpragmaディレクティブなど処理系定義の手段を用いる場合は、標準Cの範囲外ですからそれぞれの定義に従います。

投稿2018/09/22 15:05

yohhoy

総合スコア6189

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

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

otn

2018/09/23 00:42

これは共用体の話なので直接は関係ないですね。 しかし、この Two structures 以下の文言は、質問文の > メンバの内容が同じ構造体の宣言をふたつしたとき、このふたつの構造体が同じレイアウトを持つ を前提としないと無意味な文言になるので、間接的に保証されていると言うことでしょう。
guest

0

純粋なC99ではありませんが、うまくいかなくなるようにすることはできます。

C

1#include <stdio.h> 2 3typedef struct foo { 4 char a; 5 int b; 6} foo; 7 8#pragma pack(1) 9typedef struct bar { 10 char c; 11 int d; 12} bar; 13 14int main(void) 15{ 16 foo foo1 = {1, 2}; 17 bar bar1 = {3, 4}; 18 19 foo *foo2 = (foo *)&bar1; 20 bar *bar2 = (bar *)&foo1; 21 22 printf("%d, %d\n", foo2->b, bar2->d); 23 24 return 0; 25}

※ Windows 10 64bit環境、Visual C++ 2017とMinGW-w64 GCC 8.2.0で確認

#pragma pack(1)はパッキングアライメントを1に変更するコンパイラ固有の機能です。Visual C++であれば/Zpオプションでも変更することが可能です。つまり、同じソースコード、同じ環境、同じコンパイラであっても、コンパイルオプションが違うだけでパディングのサイズが変わると言うことです。よって、全く以てレイアウトは保証されていません。

でも、実際にこれで困ることはほとんどありません。同じプラットフォーム、同じアーキテクチャであれば、コンパイラ間のデフォルトのパッキングアライメントを同じにしているからです。他のコンパイラがそのプラットフォームのデファクトスタンダートに合わせている(WindowsならVisual C++、LinuxならGCCででしょう)とも言えるでしょう。(昔、そういった物を合わせないコンパイラがあったような気がしますが、ちょっと調べられなかったです)

これでは不味いと言うことで、C11からアライメントを調整できるalignasが追加されました。ただ、上の#pragma pack(1)と完全に同じことが出来るというわけでもないようです。C++11でもaligasが追加されていますが、C11とは使い方が異なります。ここら辺が標準規格でまとまるのはもう少し時間がかかると思います。それまではコンパイラ固有の機能で調整するしかないと思います。

投稿2018/09/22 01:07

編集2018/09/22 01:36
raccy

総合スコア21733

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

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

asm

2018/09/22 01:34

#pragma pack(1)をした後に戻さない行儀の悪いヘッダをインクルードした際に 困ることがあったような記憶があります。
guest

0

まず、質問文のコードは Visual stdio 2017 Community では通らなかった。
(cl.exe test.c)
/Tp オプションで 改善しましたが、構造体のキャストは型変換できませんエラー(C2440)となりました。

中身が同じでも 型が違うので、エラーが当然と思うが、根拠が思い出せない。
C99 より前で、NGと書かれた記述を見たことがあるのですが、、、。
C99はそれより、当然、厳しくなっている筈なので、難しいと思います。
探してみたいと思いますが、Visual studioでの結果について報告。

投稿2018/09/21 23:40

pepperleaf

総合スコア6383

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

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

SaitoAtsushi

2018/09/22 03:04

6.5.16.1 で書かれている条件に当てはまらないので、これは明示的キャストが必要な事例でした。
guest

0

メンバの内容が同じ構造体の宣言をふたつしたとき、このふたつの構造体が同じレイアウトを持つことを保証するような文言は仕様にあるでしょうか?

同じコンパイラで同じコンパイルオプションでコンパイルした場合の話ということでいいですよね?
明確な文言があるかどうかはわかりませんが、保証されているはずです。そうでなければ、既存のCのプログラムはほとんど動作しません。

その詰め物 (パディング) の仕方には制約が書かれていないように見えます。

コンパイラやコンパイルオプションが変われば、詰め物のサイズが変わってレイアウトが一致しない可能性があると思いますが、質問のソースコードの例では一つのソースコード内に宣言されている構造体ですので、詰め物のサイズは必ず一致するはずです。

投稿2018/09/21 23:05

mit0223

総合スコア3401

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

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

pepperleaf

2018/09/21 23:26

> 既存のCのプログラムはほとんど動作しません。 具体例はあるでしょうか? 違う気がします。 ただ、Cの場合、強力(?)なキャストがあり、Waring無視のコード(特に古いコード)があるので、問題は顕在しずらいと思いますが。
mit0223

2018/09/22 00:05

> 具体例はあるでしょうか? 型と順序が同じであればメンバ変数のオフセットが変わらないことを前提とした構造体ポインタのキャストというのは昔から行われてると思います。たとえば、以下のように。 http://www.wisdomsoft.jp/343.html
guest

0

えっと、エラーを取って実行してみました。

usr~/test/c % cc ct0.c ct0.c:18:15: warning: incompatible pointer types initializing 'struct foo *' with an expression of type 'struct bar *' [-Wincompatible-pointer-types] struct foo* foo2 = &bar1; ^ ~~~~~ ct0.c:19:15: warning: incompatible pointer types initializing 'struct bar *' with an expression of type 'struct foo *' [-Wincompatible-pointer-types] struct bar* bar2 = &foo1; ^ ~~~~~ ct0.c:5:7: warning: padding struct 'struct foo' with 3 bytes to align 'b' [-Wpadded] int b; ^ ct0.c:10:7: warning: padding struct 'struct bar' with 3 bytes to align 'd' [-Wpadded] int d; ^ 4 warnings generated. usr~/test/c % ./a.out 4, 2 usr~/test/c % cat ct0.c #include <stdio.h> struct foo { char a; int b; }; struct bar { char c; int d; }; int main(void) { struct foo foo1 = {1, 2}; struct bar bar1 = {3, 4}; struct foo* foo2 = &bar1; struct bar* bar2 = &foo1; printf("%d, %d\n", foo2->b, bar2->d); return 0; }

処理系依存&CPUのアーキテクチャ依存だと思いますd^^
この辺はデータ構造アライメントに詳しいです、
・・環境はLinux mint 8.13 + clang version 8.0.0 (trunk 341621)
[追記]
処理系がわからない.dllや.soで構造体などを共有する場合はヘッダファイルがあってもちゃんとデバッグしないと怖いですね ^^;

投稿2018/09/21 20:32

編集2018/09/21 20:46
cateye

総合スコア6851

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問