いくつかの予備的なポイント:
main
これはvoid
、標準に関する限り、プログラムの動作が定義されていないことを意味します。void
しかし、あなたの実装がreturn from を明示的に許可していると仮定しましょうmain
。また、UBmemcpy
を含めなかったので、スコープ内にプロトタイプはありません。string.h
の値buffer
は決して使用されないため、実際にはプログラム全体に観察可能な動作はありません。したがって、コンパイラはコードの一部またはすべてを削除できます。何もしない 1 つのプログラムは、標準に関する限り、別のプログラムと同じくらい優れています。
の後の2*sizeof(int)
バイトを出力するプログラムを作成したふりをします。buffer
memcpy
#include <string.h>
#include <stdio.h>
char buffer[256];
struct stX
{
int a;
int b;
int c;
};
int main()
{
struct stX x;
x.a = 1;
x.b = 2;
x.c = 3;
memcpy(buffer, &x.b, 2 * sizeof(int));
{
int i;
for (i = 0; i < 2 * sizeof(int); ++i) {
printf("%d\n", buffer[i]);
}
}
return 0; /* just in case this is C89 */
}
したがって、私が理解しているように、コンパイラx.a
はこのコードでへの割り当てを削除しても問題ありませんが、への割り当てを削除するx.c
と、コンパイラが標準に準拠していないことを意味します。構造体に奇妙なパディングが含まれていない限り。
との間に少なくともsizeof(int)
バイトのパディングがあれば、への割り当てを削除しても問題ありません。標準ではそのようなパディングが許可されていますが、使用している実際の実装にはそれがないと安全に推測できると思います;-)b
c
struct stX
x.c
説明は、 、、およびx
の値を持つ、抽象マシンで定義された状態を持つことです。メモリ内のバイトのシーケンスである「オブジェクト表現」もあります。のアドレスから始まる、これらのバイトのアクセスへの呼び出し。これはすべて正当であり、構造体の末尾から少なくともバイトが戻っていることが保証されています。これは、C が構造体のデータ メンバーが定義されている順序で配置されることを保証しているためです。だから、後に来なければなりません。実装が持つ唯一の自由は、それが直後に来るかどうか、またはそれらの間にパディングがあるかどうかです。a
b
c
x
sizeof(stX)
memcpy
2*sizeof(int)
x.b
x.b
2*sizeof(int)
c
b
コンパイラは、 が標準でどのように定義されているかを「知る」ことが許可されているため、 の前にあるため、へのこの呼び出しは のバイトのいずれにもアクセスしないmemcpy
と推測できます。これにより、物理メモリ内のバイトに抽象マシン内の値が含まれないように最適化できます。ただし、 への呼び出しが のバイトにアクセスしないと推測することは許可されていません(構造体にそれほど多くのパディングがない限り)。そのため、これらのバイトの適切な値を にコピーすることを省略することは許可されていません。memcpy
x.a
x.b
memcpy
x.c
buffer
の末尾を超えて読み取られた追加のバイトがx.b
の内容ではなくパディングであった場合x.c
、もちろん、への割り当ての有無はx.c
無関係になるため、省略できます。
別の正当な最適化は、コードを次のように変換することです。
int main() {
((int*)buffer)[0] = 2;
((int*)buffer)[1] = 3;
... same printf loop as before ...
}
つまり、物理メモリのx
どこにでも「実際に存在する」必要はありませんが、標準では、実装で表現されてbuffer
いる方法に従って、出力されるバイトが正しいバイトである必要があります。int
そして、実装でどのようstruct stX
にレイアウトされているか。