Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/59.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
是gcc';s _属性_((打包))/#pragma pack不安全?_C_Gcc_Pragma Pack - Fatal编程技术网

是gcc';s _属性_((打包))/#pragma pack不安全?

是gcc';s _属性_((打包))/#pragma pack不安全?,c,gcc,pragma-pack,C,Gcc,Pragma Pack,在C语言中,编译器将按照声明的顺序排列结构的成员,在成员之间或最后一个成员之后插入可能的填充字节,以确保每个成员正确对齐 gcc提供了一个语言扩展,\uuuuuuu属性((打包)),它告诉编译器不要插入填充,从而允许结构成员未对齐。例如,如果系统通常要求所有int对象具有4字节对齐方式,\uuuuuu属性((打包))可能导致int结构成员以奇数偏移量分配 引用gcc文件: “packed”属性指定变量或结构字段 应该有最小可能的对齐方式--一个变量一个字节, 一个字段一位,除非用 `“对齐”属性

在C语言中,编译器将按照声明的顺序排列结构的成员,在成员之间或最后一个成员之后插入可能的填充字节,以确保每个成员正确对齐

gcc提供了一个语言扩展,
\uuuuuuu属性((打包))
,它告诉编译器不要插入填充,从而允许结构成员未对齐。例如,如果系统通常要求所有
int
对象具有4字节对齐方式,
\uuuuuu属性((打包))
可能导致
int
结构成员以奇数偏移量分配

引用gcc文件:

“packed”属性指定变量或结构字段 应该有最小可能的对齐方式--一个变量一个字节, 一个字段一位,除非用 `“对齐”属性

显然,使用此扩展会导致较小的数据需求,但会导致较慢的代码,因为编译器必须(在某些平台上)生成代码以每次访问一个未对齐的成员一个字节

但是,在任何情况下,这是不安全的吗?编译器是否总是生成正确(尽管速度较慢)的代码来访问压缩结构的未对齐成员?它在所有情况下都可以这样做吗?

是的,
\uuuuuu属性((打包))
在某些系统上可能不安全。症状可能不会出现在x86上,这只会使问题更加隐蔽;在x86系统上进行测试不会暴露问题。(在x86上,未对齐的访问在硬件中处理;如果您取消引用指向奇数地址的
int*
指针,它将比正确对齐的指针慢一点,但您会得到正确的结果。)

在其他一些系统上,如SPARC,试图访问未对齐的
int
对象会导致总线错误,导致程序崩溃

也有一些系统,未对齐的访问会悄悄地忽略地址的低位,导致它访问错误的内存块

考虑以下计划:

#include <stdio.h>
#include <stddef.h>
int main(void)
{
    struct foo {
        char c;
        int x;
    } __attribute__((packed));
    struct foo arr[2] = { { 'a', 10 }, {'b', 20 } };
    int *p0 = &arr[0].x;
    int *p1 = &arr[1].x;
    printf("sizeof(struct foo)      = %d\n", (int)sizeof(struct foo));
    printf("offsetof(struct foo, c) = %d\n", (int)offsetof(struct foo, c));
    printf("offsetof(struct foo, x) = %d\n", (int)offsetof(struct foo, x));
    printf("arr[0].x = %d\n", arr[0].x);
    printf("arr[1].x = %d\n", arr[1].x);
    printf("p0 = %p\n", (void*)p0);
    printf("p1 = %p\n", (void*)p1);
    printf("*p0 = %d\n", *p0);
    printf("*p1 = %d\n", *p1);
    return 0;
}
在带有gcc 4.5.1的SPARC Solaris 9上,它生成以下内容:

sizeof(struct foo)      = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = ffbff317
p1 = ffbff31c
Bus error
struct  __attribute__((__packed__)) my_struct {
    char c;
    int i;
};

struct my_struct a = {'a', 123};
struct my_struct *b = &a;
int c = a.i;
int d = b->i;
int *e __attribute__((aligned(1))) = &a.i;
int *f = &a.i;
在这两种情况下,程序的编译都没有额外的选项,只是
gcc-packed.c-o-packed

(使用单个结构而不是数组的程序不会可靠地显示问题,因为编译器可以在奇数地址上分配结构,以便
x
成员正确对齐。对于两个
struct foo
对象的数组,至少一个或另一个将有一个未对齐的
x
成员。)

(在本例中,
p0
指向未对齐的地址,因为它指向
char
成员后面的压缩
int
成员。
p1
恰好正确对齐,因为它指向数组第二个元素中的同一个成员,所以在它前面有两个
char
对象,在SPARC上也是如此。)拉里斯阵列
arr
似乎分配在一个偶数地址,但不是4的倍数。)

当按名称引用
struct foo
的成员
x
时,编译器知道
x
可能未对齐,并将生成其他代码以正确访问它

一旦
arr[0].x
arr[1].x
的地址存储在指针对象中,编译器和运行的程序都不知道它指向未对齐的
int
对象。它只是假设它正确对齐,导致(在某些系统上)总线错误或类似的其他故障

我认为,在gcc中解决这个问题是不切实际的。一般的解决方案要求,每次尝试将指针解引用到具有非平凡对齐要求的任何类型时,(A)在编译时证明指针不指向压缩结构的未对齐成员,或(b)生成体积更大、速度更慢的代码,可以处理对齐或未对齐的对象

我已经提交了一个。正如我所说的,我不相信修复它是可行的,但是文档应该提到它(目前没有)

更新:从2018年12月20日起,此错误被标记为已修复。该补丁将出现在gcc 9中,并添加一个新的
-Waddress of packed member
选项,默认情况下启用

当获取结构或联合的打包成员的地址时,它可能 导致指针值未对齐。此修补程序添加 -打包成员的地址,以检查指针分配时的对齐情况,并警告未对齐的地址和未对齐的指针

我刚刚从源代码构建了该版本的gcc。对于上述程序,它生成以下诊断:

c.c: In function ‘main’:
c.c:10:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
   10 |     int *p0 = &arr[0].x;
      |               ^~~~~~~~~
c.c:11:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
   11 |     int *p1 = &arr[1].x;
      |               ^~~~~~~~~

只要始终通过
(点)或
->
符号通过结构访问值,就完全安全了

不安全的做法是使用未对齐数据的指针,然后在不考虑该指针的情况下访问它


此外,即使已知结构中的每个项都未对齐,但已知它以特定的方式未对齐,因此结构作为一个整体必须按照编译器的预期对齐,否则会出现问题(在某些平台上,或者将来如果发明了一种新方法来优化未对齐的访问).

正如ams上面所说,不要将指针指向已打包的结构的成员。这只是玩火。当你说
\uuuu属性((\uuu packed\uuu))
\pragma pack(1)
时,你真正说的是“嘿,gcc,我真的知道我在做什么。”当事实证明您没有这样做时,您不能正确地责怪编译器

也许我们可以把它的自满归咎于编译器。虽然gcc确实有一个
-Wcast align
选项,但它在默认情况下没有启用,也没有使用
-Wall
-Wextra
。这显然是由于gcc开发人员考虑了这一点
struct  __attribute__((__packed__)) my_struct {
    char c;
    int i;
};

struct my_struct a = {'a', 123};
struct my_struct *b = &a;
int c = a.i;
int d = b->i;
int *e __attribute__((aligned(1))) = &a.i;
int *f = &a.i;
0: message type (1 byte)
1: target address, MSB
2: target address, LSB
3: data (chars)
...
F: checksum (1 byte)
typedef struct {
  uint8_t msgType;
  uint16_t targetAddr; // may have to bswap
  uint8_t data[12];
  uint8_t checksum;
} __attribute__((packed)) myStruct;
union {
    struct {
        int    alltypes;
    }n;
    struct {
        int    type;
        int    intnode;
    } ni;
    struct {
        int    type;
        double doublenode;
    } nf;
}u;
u.nf.type = 1;
u.nf.doublenode = 3.14;
/*
...
*/
if (u.n.alltypes == 1)
if (sin(u.nf.doublenode) == 0.0)
/*
...
*/
#include <stdio.h>
#include <stdlib.h>

struct s1
{
    short a;
    int b;
} __attribute__((packed));

struct s2
{
    short a;
    int b;
};

union su {
    struct s1 x;
    struct s2 y;
};

int main()
{
    union su s;
    s.x.a = 0x1234;
    s.x.b = 0x56789abc;

    printf("sizeof s1 = %zu, sizeof s2 = %zu\n", sizeof(struct s1), sizeof(struct s2));
    printf("s.y.a=%hx, s.y.b=%x\n", s.y.a, s.y.b);
    return 0;
}
sizeof s1 = 6, sizeof s2 = 8
s.y.a=1234, s.y.b=5678