前提
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ページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答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
総合スコア84423
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
総合スコア6189
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
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総合スコア21733
0
まず、質問文のコードは Visual stdio 2017 Community では通らなかった。
(cl.exe test.c)
/Tp オプションで 改善しましたが、構造体のキャストは型変換できませんエラー(C2440)となりました。
中身が同じでも 型が違うので、エラーが当然と思うが、根拠が思い出せない。
C99 より前で、NGと書かれた記述を見たことがあるのですが、、、。
C99はそれより、当然、厳しくなっている筈なので、難しいと思います。
探してみたいと思いますが、Visual studioでの結果について報告。
投稿2018/09/21 23:40
総合スコア6383
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
メンバの内容が同じ構造体の宣言をふたつしたとき、このふたつの構造体が同じレイアウトを持つことを保証するような文言は仕様にあるでしょうか?
同じコンパイラで同じコンパイルオプションでコンパイルした場合の話ということでいいですよね?
明確な文言があるかどうかはわかりませんが、保証されているはずです。そうでなければ、既存のCのプログラムはほとんど動作しません。
その詰め物 (パディング) の仕方には制約が書かれていないように見えます。
コンパイラやコンパイルオプションが変われば、詰め物のサイズが変わってレイアウトが一致しない可能性があると思いますが、質問のソースコードの例では一つのソースコード内に宣言されている構造体ですので、詰め物のサイズは必ず一致するはずです。
投稿2018/09/21 23:05
総合スコア3401
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/09/21 23:26
2018/09/22 00:05
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総合スコア6851
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/09/22 00:14