为什么人们不应该隐瞒的结构实现呀? [英] Why one should not hide a structure implementation that way?

查看:82
本文介绍了为什么人们不应该隐瞒的结构实现呀?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我见过用了一招用同样大小的一个不透明的(阴影)结构隐藏结构实现了一些C / C ++ code:

I've seen some C/C++ code using a trick to hide structure implementation using an opaque (shadow) structure of the same size:

private.h ,结构的准确实施宣称:

In private.h, the exact implementation of the structure is declared:

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 ,公众结构被宣布为持有字节数组不透明:

In public.h, the public structure is declared to hold an opaque array of bytes:

#include "private.h"

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

public_t private_t 共享相同的尺寸。

用户可以使用公共结构分配存储自理私人执行:

Users can allocate themself storage for private implementation using the public structure:

#include <public.h>

int main(void)
{
    public_t pub;

    return public_api(&pub);
}

实施可以访问隐藏的实现:

Implementation can access the hidden implementation:

#include "private.h"

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

    return priv->val;
}

这似乎是一个pretty巧妙的技巧,让用户变量分配存储空间(如声明静态变量)。

This seems a pretty neat trick to allow users to allocate storage for variables (eg. declare static variables).

我移植使用各种嵌入式系统的这一招专有源$ C ​​$ C,但我觉得不舒服的方式结构自信 pub_t 声明。

I'm porting proprietary source code using this trick on various embedded system, but I'm not feeling confident in the way structure pub_t is declared.

有什么可以不对这一招?

What can be wrong with this trick ?

推荐答案

谨防对齐的!

public_t 本地对齐是因为字符 1对齐到1个字节。
private_t 对齐方式设置为成员的最高对齐要求,这肯定不是1。它是在一个指针的大小大概一致(无效* ),但有一个双击这可能需要8个字节对齐一个子里面。你可以看到各种各样的种类排列取决于ABI。

public_t native alignment is 1 since char are aligned to 1 byte. private_t alignment is set to the highest alignment requirement of its members, which is certainly not 1. It's probably aligned on the size of a pointer (void *), but there's a double inside a substructure which might require an alignment on 8 bytes. You may seen various kind of alignment depending on the ABI.

让我们尝试一个示例程序,与海湾合作委员会(code源跟踪)编译和测试在i386 / i686的:

Let's try a example program, compiled and tested on i386/i686 with gcc (code source follow):

     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**

测试来源$ C ​​$ C:

Source code of the test:

#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 被分配在栈上,并作为参数传递给函数测试。这是在1字节对齐,这样,当为一个 priv_t 指针, priv_t 结构成员cast'ed不对齐。

pub0 is allocated on the stack and is passed as argument to function test. It is aligned on 1 byte, so that, when cast'ed as a priv_t pointer, priv_t structure members are not aligned.

这可不好:


  • 坏的正确性:某些架构/ CPU都将默默地腐败的读/写操作未对齐的内存地址时,另一些人会产生故障。后者是更好的。

  • 坏的表现:如果支持的话,对齐访问/负载/存储仍然已知处理不善:你可能会问到的CPU读/写对象的内存大小的两倍......你会打缓存严重这种方式。

所以,如果你真的想隐藏结构的内容,你应该采取的底层结构的对齐的护理:不要使用字符

So, if you really want to hide structure content, you should take care of the alignment of the underlying structure: don't use char.

在默认情况下,使用无效* ,或者如果可以有双击结构中的任何成员,使用双击。这将工作到有人使用 #prama __属性__(())来选择一个较高的定位(成员)的隐藏的结构。

By default, use void *, or if there can be double in any members of the structure, use double. This will works until someone use a #prama or __attribute__(()) to choose an higher alignment for (a member of) the hidden structure.

让我们正确地定义 pub_t

typedef struct pub
{
    double opaque[(sizeof(priv_t) + (sizeof(double) - 1)) / sizeof(double)];
} pub_t;

这听起来复杂,它是!这样, pub_t 结构将有正确的定位,并至少一样大的潜在 priv_t

It might sound complex, and it is ! This way the pub_t structure will have correct alignment and be at least as big as underlying priv_t.

如果 priv_t 挤得水泄不通(用的#pragma __属性__(())),使用的sizeof(priv_t)/的sizeof(双) pub_t 可能会小于 priv_t ...这将是比我们试图首先解决的问题更坏。但是,如果结构得水泄不通,谁照顾对齐的。

If priv_t was packed (with #pragma or __attribute__(())), using sizeof(priv_t)/sizeof(double), pub_t could be smaller than priv_t ... which will be even worst than the problem we were trying to solve initially. But if the structure was packed, who care of the alignment.

的malloc()

如果 pub_t 结构由分配的malloc(),而不是在栈上被分配,对齐不会因为有问题的malloc()被定义为返回对齐到C原生类型,例如最大内存对齐内存块。 双击。在现代的malloc()实施对准可达到32字节。

If pub_t structure was allocated by malloc() instead of being allocated on stack, the alignment would not be a problem since malloc() is defined to return a memory block aligned to the greatest memory alignments of the C native types, eg. double. In modern malloc() implementations alignment could be up to 32 bytes.

这篇关于为什么人们不应该隐瞒的结构实现呀?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆