2

同じサイズの不透明 (シャドウ) 構造体を使用して構造体の実装を非表示にするトリックを使用する C/C++ コードを見てきました。

ではprivate.h、構造体の正確な実装が宣言されています。

typedef struct private_struct
{
    private_foo_t f1;
    private_bar_t b[2];
    private_baz_t *bz;
    int val;
} private_t;

#define PRIVATE_SIZE (sizeof(private_t))

ではpublic.h、 public 構造体は不透明なバイト配列を保持するように宣言されています。

#include "private.h"

typedef struct public_struct
{
    char opaque[PRIVATE_SIZE];
} public_t;

public_t同じサイズをprivate_t共有します。

ユーザーは、public 構造を使用して、プライベート実装用のストレージを自分自身に割り当てることができます。

#include <public.h>

int main(void)
{
    public_t pub;

    return public_api(&pub);
}

実装は非表示の実装にアクセスできます。

#include "private.h"

int public_api(public_t *pub)
{
    private_t *priv = (private_t *) pub;

    return priv->val;
}

これは、ユーザーが変数にストレージを割り当てられるようにする (例: 静的変数を宣言する) ためのかなり巧妙なトリックのようです。

pub_tさまざまな組み込みシステムでこのトリックを使用して独自のソース コードを移植していますが、構造体の宣言方法に自信がありません。

このトリックのどこが悪いのでしょうか?

4

3 に答える 3

8

アライメントに注意!

public_tchar1 バイトにアラインされている ため、ネイティブ アラインメントは 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 *存在する可能性がある場合はを使用します。これは、誰かがまたはを使用して、非表示の構造 (のメンバー) のより高い配置を選択するまで機能します。doubledouble#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_tpriv_t

malloc()

pub_t構造体がスタックに割り当てられるのではなく によって割り当てられた場合、C ネイティブ型の最大のメモリ アラインメントにアラインされたメモリ ブロックを返すように定義されてmalloc()いるため、アラインメントは問題になりません。. 最新の実装では、アラインメントは最大 32 バイトです。malloc()doublemalloc()

于 2013-07-12T15:56:20.520 に答える
3

ほとんどの場合、内部構造の性質は公開されていません。これは、それを使用しているすべてのコードを再コンパイルすることなく自由に変更できるようにするためです。そして、まさにそれが、あなたが言及したトリックを使用する場合に失うものであり、サイズがprivate_t変化します. したがって、無料であるためにはalloc_struct()、構造体を割り当てて avoid *を返すような関数、またはsizeof(private_t)割り当てに使用できるように返す関数のいずれかを提供することをお勧めします…</p>

于 2013-07-12T16:06:29.067 に答える