アライメントに注意!
public_t
char
1 バイトにアラインされている
ため、ネイティブ アラインメントは 1です。private_t
アラインメントは、そのメンバーの最高のアラインメント要件に設定されていますが、これは確かに 1 ではありません。おそらくポインター ( void *
)のサイズでアラインされていますdouble
が、8 バイトでのアラインメントが必要な可能性があるサブ構造内にあります。ABI によって、さまざまな種類のアライメントが見られる場合があります。
gcc を使用して i386/i686 でコンパイルおよびテストされたサンプル プログラムを試してみましょう (コード ソースは次のとおりです)。
kind name address size alignment required
type | foo_t | N/A | 48 | N/A | 4
type | priv_t | N/A | 56 | N/A | 4
type | pub_t | N/A | 56 | N/A | 1
object | u8_0 | 0xfff72caf | 1 | 1 | 1
object | u8_1 | 0xfff72cae | 1 | 2 | 1
object | u8_2 | 0xfff72cad | 1 | 1 | 1
object | pub0 | 0xfff72c75 | 56 | 1 | 1
object | u8_3 | 0xfff72c74 | 1 | 4 | 1
object | pub1 | 0xfff72c3c | 56 | 4 | 1
object | u8_4 | 0xfff72c3b | 1 | 1 | 1
object | priv0 | 0xfff72c00 | 56 | 1024 | 4
object | u8_5 | 0xfff72bff | 1 | 1 | 1
object | priv1 | 0xfff72bc4 | 56 | 4 | 4
object | u8_6 | 0xfff72bc3 | 1 | 1 | 1
pointer | pubp | 0xfff72c75 | 56 | 1 | 1
pointer | privp | 0xfff72c75 | 56 | 1 | 4 **UNALIGNED**
object | privp->val | 0xfff72c75 | 4 | 1 | 4 **UNALIGNED**
object | privp->ptr | 0xfff72c79 | 4 | 1 | 4 **UNALIGNED**
object | privp->f | 0xfff72c7d | 48 | 1 | 4 **UNALIGNED**
テストのソース コード:
#include <stdalign.h>
#ifdef __cplusplus
/* you will need to pass -std=gnu++11 to g++ */
#include <cstdint>
#endif
#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>
#ifdef __cplusplus
#define alignof __alignof__
#endif
#define PRINTHEADER() printheader()
#define PRINTSPACE() printspace()
#define PRINTALIGN(obj) printobjalign("object", #obj, &obj, sizeof(obj), alignof(obj))
#define PRINTALIGNP(ptr) printobjalign("pointer", #ptr, ptr, sizeof(*ptr), alignof(*ptr))
#define PRINTALIGNT(type) printtypealign(#type, sizeof(type), alignof(type))
static void
printheader(void)
{
printf(" %8s %10s %18s %4s %9s %8s\n", "kind", "name", "address", "size", "alignment", "required");
}
static void
printspace(void)
{
printf(" %8s %10s %18s %4s %9s %8s\n", "", "", "", "", "", "");
}
static void
printtypealign(const char *name, size_t szof, size_t alof)
{
printf(" %8s | %10s | %18s | %4zu | %9s | %8zu \n", "type", name, "N/A", szof, "N/A", alof);
}
static void
printobjalign(const char *tag, const char *name, const void * ptr, size_t szof, size_t alof)
{
const uintptr_t uintptr = (uintptr_t)ptr;
uintptr_t mask = 1;
size_t align = 0;
/* get current alignment of the pointer */
while(mask != UINTPTR_MAX) {
if ((uintptr & mask) != 0) {
align = (mask + 1) / 2;
break;
}
mask <<= 1;
mask |= 1;
}
printf(" %8s | %10s | %18p | %4zu | %9zu | %8zu%s\n",
tag, name, ptr, szof, align, alof, (align < alof) ? " **UNALIGNED**" : "");
}
/* a foo struct with various fields */
typedef struct foo
{
uint8_t f8_0;
uint16_t f16;
uint8_t f8_1;
uint32_t f32;
uint8_t f8_2;
uint64_t f64;
uint8_t f8_3;
double d;
uint8_t f8_4;
void *p;
uint8_t f8_5;
} foo_t;
/* the implementation struct */
typedef struct priv
{
uint32_t val;
void *ptr;
struct foo f;
} priv_t;
/* the opaque struct */
typedef struct pub
{
uint8_t padding[sizeof(priv_t)];
} pub_t;
static int
test(pub_t *pubp)
{
priv_t *privp = (priv_t *)pubp;
PRINTALIGNP(pubp);
PRINTALIGNP(privp);
PRINTALIGN(privp->val);
PRINTALIGN(privp->ptr);
PRINTALIGN(privp->f);
PRINTSPACE();
return privp->val;
}
int
main(void)
{
uint8_t u8_0;
uint8_t u8_1;
uint8_t u8_2;
pub_t pub0;
uint8_t u8_3;
pub_t pub1;
uint8_t u8_4;
priv_t priv0;
uint8_t u8_5;
priv_t priv1;
uint8_t u8_6;
PRINTHEADER();
PRINTSPACE();
PRINTALIGNT(foo_t);
PRINTALIGNT(priv_t);
PRINTALIGNT(pub_t);
PRINTSPACE();
PRINTALIGN(u8_0);
PRINTALIGN(u8_1);
PRINTALIGN(u8_2);
PRINTALIGN(pub0);
PRINTALIGN(u8_3);
PRINTALIGN(pub1);
PRINTALIGN(u8_4);
PRINTALIGN(priv0);
PRINTALIGN(u8_5);
PRINTALIGN(priv1);
PRINTALIGN(u8_6);
PRINTSPACE();
return test(&pub0);
}
分析:
pub0
スタックに割り当てられ、 function に引数として渡されますtest
。これは 1 バイトでアラインされるため、priv_t
ポインターとしてキャストされた場合、priv_t
構造体メンバーはアラインされません。
そして、それは悪いことかもしれません:
- 正確性が悪い: 一部のアーキテクチャ/CPU は、整列されていないメモリ アドレスへの読み取り/書き込み操作をサイレントに破損し、他の一部はエラーを生成します。後者の方が良いです。
- パフォーマンスが悪い: サポートされていても、アラインされていないアクセス/ロード/ストアの処理が不十分であることが知られています: オブジェクトのメモリ サイズの 2 倍の読み取り/書き込みを CPU に要求している可能性があります ... この方法では、キャッシュにひどくヒットする可能性があります.
したがって、本当に構造体の内容を隠したい場合は、基礎となる構造体の位置合わせに注意する必要があります: を使用しないでくださいchar
。
デフォルトでは、 を使用するか、構造体のメンバーにvoid *
存在する可能性がある場合はを使用します。これは、誰かがまたはを使用して、非表示の構造 (のメンバー) のより高い配置を選択するまで機能します。double
double
#prama
__attribute__(())
正しく定義しましょうpub_t
:
typedef struct pub
{
double opaque[(sizeof(priv_t) + (sizeof(double) - 1)) / sizeof(double)];
} pub_t;
複雑に聞こえるかもしれません。このようにして、pub_t
構造は正しい位置合わせになり、少なくとも基になる と同じ大きさになりますpriv_t
。
が (またはで)priv_t
パックされている場合、を使用すると ...よりも小さくなる可能性があり、これは最初に解決しようとしていた問題よりもさらに深刻です。しかし、構造が詰め込まれている場合、誰が配置を気にしますか。#pragma
__attribute__(())
sizeof(priv_t)/sizeof(double)
pub_t
priv_t
malloc()
pub_t
構造体がスタックに割り当てられるのではなく によって割り当てられた場合、C ネイティブ型の最大のメモリ アラインメントにアラインされたメモリ ブロックを返すように定義されてmalloc()
いるため、アラインメントは問題になりません。. 最新の実装では、アラインメントは最大 32 バイトです。malloc()
double
malloc()