C 通过typedef对数组进行私有化的结构内容上的不对齐可能性

C 通过typedef对数组进行私有化的结构内容上的不对齐可能性,c,arrays,struct,alignment,zeromq,C,Arrays,Struct,Alignment,Zeromq,我使用了这种有趣的方法来创建不透明和私有结构: struct s_vector_private { size_t item_size; uint32_t used_slots; uint32_t buffer_total_slots; uint8_t * buffer; }; typedef uint8_t vector_t[sizeof(struct s_vector_private)]; /* This code can be found at [1] */

我使用了这种有趣的方法来创建不透明和私有结构:

struct s_vector_private
{
    size_t item_size;
    uint32_t used_slots;
    uint32_t buffer_total_slots;
    uint8_t * buffer;
};
typedef uint8_t vector_t[sizeof(struct s_vector_private)];
/* This code can be found at [1] */
这样,
vector\u t
内容是私有的,可以在堆和堆栈上分配
vector\u t

最近,我想知道在堆栈或堆中分配向量是否会导致对齐问题,因为编译器认为它是数组而不是结构

为了进一步说明,有一些大型库使用这种方法,比如
zmq\u msg\u t
类型的zmq声明:

typedef struct zmq_msg_t {unsigned char _ [40];} zmq_msg_t; 
/* This code can be found at [2] */
在zmq示例中,是否也可能获得未对准的结构?我不可避免地注意到
zmq\u msg\u t
已经在一个结构中声明了数组,这是一种安全的方法来实现这里讨论的技术吗

[1] ,第48行


[2] ,第202行。

这里没有问题。要了解原因,您需要了解
sizeof
如何处理没有
\uuuu属性((\uuu packed\uuuu))
或显式对齐的
struct
s

请尝试以下程序:

#include <stdio.h>
#include <stdlib.h>

struct st
{
  void *p;
  char c;
};

struct st arr[5];

int
main (int argc, char **argv)
{
  printf ("Size of struct: %lu\nSize of array: %lu\n",
      sizeof (struct st),
      sizeof (arr));
  exit (0);
}
这个稍微令人惊讶的结果是,虽然
struct st
似乎只需要9个字节(8个用于指针,1个用于
char
),但实际上需要16个字节,这正是它们的数组必须正确对齐的原因<因此,code>sizeof包含填充字节。因此,其中5个指针占用80字节,并且每个指针都正确对齐

即使在结构中,如果没有
\uuuuuuu属性((\uuuu packed\uuuu))
,也会插入填充字节以正确对齐类型(防止这正是
\uuuuu属性((\uuu packed\uuuu))
的目的)


关于ZMQ示例,我不熟悉代码,但我猜他们正在定义消息的wire格式,它具有恒定的字节大小。在某个时刻,他们会将部分或全部内容转换为一个结构,可能带有
\uuuuu attribute\uuuuuu((\uuu packed\uuuuu))
,注意什么落在哪里。为什么要这样做,而不仅仅是为整个消息定义一个结构?这可能是因为,如果有人更改
结构
,他们会立即失败,而不是使用正常但不兼容的协议。但这只是猜测。

有必要使用C11或编译器扩展来对齐
向量。例如,对于具有灵活大小和对齐方式的真正不透明类型,这意味着一个声明符如下:

#define stack_alloc_opaque(identifier) \
    alignas(alignof(max_align_t)) char (identifier)[sizeof_opaque_type()]
其中
sizeof\u opaque\u type()
是一个在运行时返回真实大小的函数

如果您不需要不透明类型的全部灵活性,则必须至少通过
alignof(struct s\u vector\u private)
对齐,但这限制了您在幕后更改类型的能力

虽然在x86上可能不会出现对齐不匹配的情况,但在具有不同对齐要求的指针之间转换是未定义的行为,访问这样的对象肯定是不可移植的

说到这里,房间里有一头大象

执行上述操作将使类型在字节级别完全兼容,但它避开了严格的别名规则。允许
char*
别名任何其他类型的例外情况是单向的--
vector\u t
被声明为
uint8\u t[]
(甚至
char[]
)使其成为有效类型,因此它不能被双关到其他类型。这给您留下了一些选择:

  • s\u vector\u private
  • 完全禁用严格别名优化(
    -fno strict aliasing
  • 让它们保持开启状态,但仔细测试,注意将来或其他配置中可能会出现问题
  • 不要在堆栈上使用不透明类型

不幸的是,唯一真正便携和安全的是最后一个。我也喜欢这种方法,但我不知道有什么其他方法可以解决这个问题。

这种技术不会导致堆分配问题。
malloc()
等返回的指针已充分对齐,可用于任何用途,
malloc()
函数无法判断它是在分配可能位于任何字节边界上的40字节字符串还是需要在8字节边界上对齐的类型数组,因此,它采取保守的观点,即它总是分配实现所需的最严格的对齐

理论上,该技术可能会导致堆栈分配问题。如果您有:

char     c1;
vector_t v1;
char     c2;
然后,您可能会发现
v1
未对齐。这可能不会是一个问题,但如果你希望安全,你可以避免这种可能性。然而,在Mac OS X 10.9.1上运行的GCC 4.8.2似乎在一定程度上避免了麻烦:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror alx.c -o alx
$ alx
24
&c1 = 0x7fff54b9552d
&v1 = 0x7fff54b95530
&c2 = 0x7fff54b9552e
&p1 = 0x7fff54b95550
&c3 = 0x7fff54b9552f
$ gcc -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror alx.c -o alx
$ alx
24
&c1 = 0x7fff579a756f
&v1 = 0x7fff579a7550
&c2 = 0x7fff579a754f
&p1 = 0x7fff579a7530
&c3 = 0x7fff579a752f
$
#include <stdint.h>
#include <stdio.h>

struct s_vector_private
{
    size_t item_size;
    uint32_t used_slots;
    uint32_t buffer_total_slots;
    uint8_t *buffer;
};
typedef uint8_t vector_t[sizeof(struct s_vector_private)];

int main(void)
{
    char c1;
    vector_t v1;
    char c2;
    struct s_vector_private p1;
    char c3;

    printf("%zu\n", sizeof(vector_t));
    printf("&c1 = %p\n", (void *)&c1);
    printf("&v1 = %p\n", (void *)&v1);
    printf("&c2 = %p\n", (void *)&c2);
    printf("&p1 = %p\n", (void *)&p1);
    printf("&c3 = %p\n", (void *)&c3);

    return 0;
}
$gcc-O3-g-std=c11-Wall-Wextra-Wmissing原型-Wstrict原型-Wold样式定义-Werror alx.c-o alx
$alx
24
&c1=0x7fff54b9552d
&v1=0x7fff54b95530
&c2=0x7fff54b9552e
&p1=0x7fff54b95550
&c3=0x7fff54b9552f
$gcc-g-std=c11-Wall-Wextra-Wmissing原型-Wstrict原型-Wold样式定义-Werror alx.c-o alx
$alx
24
&c1=0x7fff579a756f
&v1=0x7fff579a7550
&c2=0x7fff579a754f
&p1=0x7fff579a7530
&c3=0x7fff579a752f
$
#包括
#包括
结构s_向量_私有
{
尺寸\u t项目\u尺寸;
uint32_t用过的插槽;
uint32缓冲区总插槽数;
uint8_t*缓冲器;
};
typedef uint8_t vector_t[sizeof(struct s_vector_private)];
内部主(空)
{
字符c1;
向量v1;
字符c2;
结构s_向量_私有p1;
炭c3;
printf(“%zu\n”,sizeof(vector_t));
printf(“&c1=%p\n”,(void*)&c1);
printf(“&v1=%p\n”,(void*)&v1);
printf(“&c2=%p\n”,(void*)&c2);
printf(“&p1=%p\n”,(void*)&p1);
printf(“&c3=%p\n”,(void*)&c3);
返回0;
}
而且,这种技术有点奇怪。它并没有真正隐藏起来
$ gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror alx.c -o alx
$ alx
24
&c1 = 0x7fff54b9552d
&v1 = 0x7fff54b95530
&c2 = 0x7fff54b9552e
&p1 = 0x7fff54b95550
&c3 = 0x7fff54b9552f
$ gcc -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror alx.c -o alx
$ alx
24
&c1 = 0x7fff579a756f
&v1 = 0x7fff579a7550
&c2 = 0x7fff579a754f
&p1 = 0x7fff579a7530
&c3 = 0x7fff579a752f
$
#include <stdint.h>
#include <stdio.h>

struct s_vector_private
{
    size_t item_size;
    uint32_t used_slots;
    uint32_t buffer_total_slots;
    uint8_t *buffer;
};
typedef uint8_t vector_t[sizeof(struct s_vector_private)];

int main(void)
{
    char c1;
    vector_t v1;
    char c2;
    struct s_vector_private p1;
    char c3;

    printf("%zu\n", sizeof(vector_t));
    printf("&c1 = %p\n", (void *)&c1);
    printf("&v1 = %p\n", (void *)&v1);
    printf("&c2 = %p\n", (void *)&c2);
    printf("&p1 = %p\n", (void *)&p1);
    printf("&c3 = %p\n", (void *)&c3);

    return 0;
}
typedef union
{
  char __size[__SIZEOF_PTHREAD_MUTEXATTR_T];
  int __align;
} pthread_mutexattr_t;
#ifdef __x86_64__
# if __WORDSIZE == 64
#  define __SIZEOF_PTHREAD_ATTR_T 56
#  define __SIZEOF_PTHREAD_MUTEX_T 40
#  define __SIZEOF_PTHREAD_MUTEXATTR_T 4
...