gcc 的 __attribute__((packed)) / #pragma pack 不安全吗?

2023-11-27

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

gcc 提供了语言扩展,__attribute__((packed)),它告诉编译器不要插入填充,从而允许结构成员不对齐。例如,如果系统通常需要所有int对象具有 4 字节对齐,__attribute__((packed))可以引起int要在奇数偏移处分配的结构成员。

引用gcc文档:

“packed”属性指定变量或结构体字段 应该有尽可能最小的对齐方式——一个变量一个字节, 字段使用一位,除非您使用 ‘对齐’属性。

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

但有没有不安全的情况呢?编译器是否总是生成正确的(尽管速度较慢)代码来访问打包结构中未对齐的成员?是否有可能在所有情况下都这样做?


Yes, __attribute__((packed))在某些系统上可能不安全。该症状可能不会出现在 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.2 的 x86 Ubuntu 上,它会生成以下输出:

sizeof(struct foo)      = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = 0xbffc104f
p1 = 0xbffc1054
*p0 = 10
*p1 = 20

在带有 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

在这两种情况下,程序的编译都没有额外的选项,只是gcc packed.c -o packed.

(使用单个结构而不是数组的程序不能可靠地表现出该问题,因为编译器可以在奇数地址上分配结构,因此x成员已正确对齐。有两个数组struct foo物体,至少其中一个会出现未对准的情况x成员。)

(在这种情况下,p0指向未对齐的地址,因为它指向压缩的int会员关注char成员。p1恰好是正确对齐的,因为它指向数组第二个元素中的同一个成员,所以有两个char其前面的对象 — 以及 SPARC Solaris 上的阵列arr似乎分配在偶数地址,但不是 4 的倍数。)

提及会员时x of a struct foo通过名称,编译器知道x可能未对齐,并将生成额外的代码以正确访问它。

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

我认为在 gcc 中解决这个问题是不切实际的。对于每次尝试取消引用具有重要对齐要求的任何类型的指针,通用解决方案都需要(a)在编译时证明该指针不指向打包结构的未对齐成员,或者(b)生成更庞大、更慢的代码,可以处理对齐或未对齐的对象。

我已经提交了一份海湾合作委员会错误报告。正如我所说,我不认为修复它是实际的,但文档应该提到它(目前没有)。

UPDATE:截至 2018 年 12 月 20 日,此错误已标记为“已修复”。该补丁将出现在 gcc 9 中,并添加新的-Waddress-of-packed-member选项,默认启用。

当获取结构体或联合体的压缩成员的地址时,可以 导致未对齐的指针值。这个补丁增加了 -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;
      |               ^~~~~~~~~
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

gcc 的 __attribute__((packed)) / #pragma pack 不安全吗? 的相关文章

随机推荐

  • 在 Python 中对 zip 文件进行 base64 编码

    有人可以给我一些关于如何在 Python 中将 zip 文件编码为 base64 的建议吗 有关于如何使用模块 base64 在 Python 中对文件进行编码的示例 但我还没有找到任何有关 zipfile 编码的资源 Thanks 这与编
  • Symfony2 功能测试选择复选框

    我在编写 Symfony 2 功能测试来设置属于数组一部分的复选框 即多个和扩展的选择小部件 时遇到问题 In the 文档例子是 form registration interests gt select array symfony co
  • 集成测试无法启动(Failsafe、Maven)

    我正在尝试使用 Maven Failsafe Plugin 使用此配置运行我的集成测试
  • Java“final”方法:它承诺什么?

    在 Java 类中 方法可以定义为final 以标记此方法不得被覆盖 public class Thingy public Thingy public int operationA this method does return That
  • 从远程 Git 存储库中删除最后一次提交

    如何从远程 Git 存储库中删除最后一次提交 例如我在日志中看不到它 如果例如git log给我以下提交历史记录 A gt B gt C gt D HEAD ORIGIN 我怎样才能去 A gt B gt C HEAD ORIGIN 请注意
  • 为什么Keras/tensorflow的sigmoid和crossentropy精度低?

    我有以下简单的神经网络 仅具有 1 个神经元 来测试计算精度sigmoid激活 binary crossentropy喀拉斯 model Sequential model add Dense 1 input dim 1 activation
  • Hive 更改位置语句不起作用

    hive gt alter table my table name set location hdfs nameservice1 foo OK Time taken 0 173 seconds hive gt alter table my
  • Xcode 重复行

    There is a Duplicate command in the Edit Menu with a default shortcut of D but it is as Halley pointed out meant for dup
  • 在 Firefox 中如何防止拖动鼠标时选择文本?

    我想知道这些拖放小部件如何取消拖动元素和页面中其他元素中的文本选择 我尝试了以下代码 该代码在 IE8 中有效 无法选择文本 但在 Firefox 中无效 仍然可以选择文本 p Hello World p 或者 类似于 Moz 的 IE8
  • 如何使一个变量的值跟踪另一个变量的值

    这是我现在拥有的一个非常简单的示例 public static class Settings public static TH th public partial class PhrasesFrame private void SetC1B
  • 如何在不删除数据库内容的情况下运行 Rails 集成测试?

    我已经编写了一些集成测试 我想在投入生产之前针对我的产品数据库副本运行这些测试 这让我可以测试所有路线是否仍然正确 所有页面渲染都没有错误 并且一些多页面工作流程按预期工作 当我运行集成测试时 它会删除我已加载的数据库并加载测试装置 如预期
  • 如何创建自定义 EL 函数来调用静态方法?

    我是 JSF 2 的新手 我的问题与 BalusC 对此问题的回答有关jsf2 ajax根据请求参数更新部分我尝试了 BalusC 发布的 kickstart 代码 遇到了 EL 解析错误 nameofpage xhtml 12 64 re
  • Java 文件:序言中不允许出现内容

    我是 Spring 新手 开始使用 Sprint Tool Suite eclipse 并在我的 Maven 项目的 java 文件中看到此错误 Prolog 中不允许内容 这会阻止该类 这只是一个最小的 SpringBootApplica
  • 布局兼容类型的目的是什么?

    标准defines当两种类型是布局兼容 但是 我在标准中没有看到当两种类型同时存在时会产生什么后果布局兼容 看起来布局兼容是一个没有在任何地方使用的定义 目的是什么布局兼容 注意 据说 这可能意味着类型具有相同的布局 offsetof对于每
  • 声明区域和范围有什么区别

    关于声明区域 每个名称都在程序文本的某个部分中引入 称为 声明区域 这是程序中最大的部分 该名称有效 关于范围 一般来说 每个特定名称仅在某些可能的范围内有效 程序文本的不连续部分称为其范围 它们之间有什么区别 声明区域是可以声明名称的地方
  • 在 64 位计算机上访问硬件 PKCS11 令牌

    这就是我正在尝试做的事情 我有一个带有一些证书的硬件令牌 我正在编写一个 Java 应用程序来尝试访问这些证书 我在 Windows 32 位机器上使用了 jre6 中的 SunPKCS11 库 这是我如何访问证书的一个小示例 String
  • 如何在 Firefox 3 中从 HTML 输入表单获取文件路径

    我们有简单的 HTML 表单
  • ASP.NET 4.5 TryUpdateModel 不使用母版页在 WebForm 中选取表单值

    我正在使用 WebForms 并且尝试在母版页内执行模型验证 由于某种原因 模型没有获取值 这意味着如果我输入一个好的值 则在验证触发后 模型会一直返回空 因此一遍又一遍地触发验证 如果我将代码放在没有母版页的页面中 它就可以正常工作 我举
  • 从 javascript 在浏览器 (Chrome) 中播放声音

    我正在写一个 html 页面 我希望它在运行时按照某些 JavaScript 的指定发出声音 在 html 中 按照我在这里阅读的答案中的建议 我有以下行 这会在加载时播放声音 因此我确信我已经给出了有效 wav 文件的有效路径 一旦一切正
  • gcc 的 __attribute__((packed)) / #pragma pack 不安全吗?

    在 C 中 编译器将按照声明的顺序布置结构体的成员 并在成员之间或最后一个成员之后插入可能的填充字节 以确保每个成员正确对齐 gcc 提供了语言扩展 attribute packed 它告诉编译器不要插入填充 从而允许结构成员不对齐 例如