【问题标题】:Understanding consequences of breaking strict-aliasing了解打破严格混叠的后果
【发布时间】:2015-01-31 10:05:36
【问题描述】:
int main()
{
    struct { int x; } foo;

    dostuff(&foo);
    return 0;
}

void dostuff(void *ptr)
{
    struct { int x; } *p = ptr;

    p->x = 5;
}

取消引用p 是违反严格别名的,因为两个未命名的结构不能相互别名,因为它们不兼容。 现在这样的代码可能/会出现什么问题?

编辑: 我仍然不确定这是否定义的行为,因为它们没有相同的标签。

假设它们兼容,以下会有什么不同吗?

union u {
    void *v;
    struct {
        int x;
    } *p;
};

void dostuff(void *ptr)
{
    union u tmp = {.v = ptr};

    tmp.p->x = 5;
}

【问题讨论】:

  • 如果您违反严格的别名,则会导致未定义的行为,这意味着任何事情都可能发生。
  • 如果这两个函数在两个不同的文件中,由不同的编译器编译(甚至同一编译器的不同版本/设置),那么您可能会遇到不同的打包/对齐等问题。跨度>
  • 阅读标准后,我不确定这些结构是否实际上不兼容。这实际上可能已定义。
  • 也许我错了。但是,正如我所看到的,a 正在将指向某些数据的指针传递给保存该指针的第二个函数。对我来说似乎没问题。当然,如果我想要这样的东西,我会在头文件中声明结构格式,并让每个 .c 文件都包含该头文件。
  • 所以如果这是未定义的行为,那么我的问题的答案是,直截了当地说:别这样,这是UB,任何事情都可能发生,对吗?但现在我不太确定它是否真的是未定义的

标签: c struct unions strict-aliasing


【解决方案1】:

您描述的场景实际上可能会出现在以下情况下:不同的模块各自声明具有特定布局的自己的结构并使用它们来交换数据,并且希望将头文件中的某些函数从“外部”范围更改为“内联”范围。不幸的是,您的场景属于编译器过去支持的有用行为类别,有时没有实际的替代方案,但标准不再强制要求支持,并且某些编译器在不禁用广泛范围的情况下不再支持除了标准允许的无用的破坏性“优化”之外,还有一些有用的优化。

【讨论】:

  • 没有实际情况要求函数声明一个本地结构类型并将其实例的地址传递给其他函数
  • @M.M:从外部函数开始,可能是用 C 以外的语言编写的,它需要指定格式的数据,但没有为其指定特定的类型名称。模块 X、Y 和 Z 都为此目的定义了自己的 typedef,它们都没有碰巧使用结构标记。如果使用单独定义的结构的所有操作都跨编译单元边界发生,则将定义行为。 X 的标头有什么方法可以在保持与 Y 和 Z 兼容的同时内联其功能?
  • @M.M:这种模式不如 C99 让编译器破坏的其他一些重要模式常见,但它符合 OP 的问题。 C89 编译器可以处理而现代 C 不能处理的更重要的情况是能够让函数对以各种大小的数组结尾的结构进行操作。除非 C89 编译器的结构布局规则以不同的方式放置不同大小的数组(这是允许的,但很少有通用平台的编译器实际上这样做),接收指向最后一个成员是数组的结构的指针的函数不会需要知道...
  • ...也不关心数组的实际大小,只要它知道它应该使用多少个元素。与其尝试为每种大小定义不同的命名结构类型,不如使用宏来声明在每种特定情况下碰巧需要的任何大小的匿名结构类型更容易且更实用。基本上在普通平台的 C89 编译器中得到普遍支持,但在“现代 C”中是不可能的。
【解决方案2】:

看起来很奇怪|疯狂|令人惊讶|自相矛盾,struct { int x; }struct { int x; } 在一个翻译单元中确实声明了不同的类型,如 e 中所述。 G。 this comment.

以下会有什么不同吗?

union u {
    void *v;
    struct {
        int x;
    } *p;
};

void dostuff(void *ptr)
{
    union u tmp = {.v = ptr};

    tmp.p->x = 5;
}

您没有在此处指定如何调用dostuff(),因此您必须拥有与第一个示例相同的main();鉴于此,如果参数在同一个翻译单元中声明,则类型仍然不兼容 - 结构类型的两个声明再次声明不同的类型,无论其中一个包含在联合中。
此外,dostuff() 的第二个版本将void *v 重新解释为struct … *p;由于 C 标准不保证指向 void 的指针与指向结构类型的指针具有相同的表示形式,因此这种用法并不严格符合。

【讨论】:

  • IIRC C 标准说,指向任何数据对象的每个指针都可以存储到 void * 引用,这与您的最后陈述相矛盾。 AFAIK 这始终可以保证工作:X * a = ...; void * v = a; X * b = (X *)v; 只是不能保证函数指针可以存储为void *,尽管它几乎适用于所有平台,但 C 标准允许数据和函数指针具有不同的表示形式.
  • @Mecki - 您将 conversionsreinterpretations (“类型双关语”)混淆了。虽然 void *struct … * 之间的隐式(赋值)或显式(类型转换)转换保证可以工作,但读取最后存储的成员以外的联合成员会将存储类型的值重新解释为另一种类型,可能是 陷阱表示.
猜你喜欢
  • 1970-01-01
  • 2019-04-10
  • 2013-06-29
  • 2011-03-03
  • 1970-01-01
  • 1970-01-01
  • 2016-10-26
  • 2023-03-15
  • 2014-02-08
相关资源
最近更新 更多