为什么人们不应该隐瞒的结构实现呀? [英] Why one should not hide a structure implementation that way?
问题描述
我见过用了一招用同样大小的一个不透明的(阴影)结构隐藏结构实现了一些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屋!