在实践中,工会,别名和打字:什么有效,有什么不可行?

L.C*_*.C. 16 c++ gcc strict-aliasing

我有一个问题,了解使用GCC的工会可以做什么和不可以做什么.我阅读了有关它的问题(特别是这里这里),但他们关注C++标准,我觉得C++标准和实践(常用的编译器)之间存在不匹配.

特别是,我最近在阅读有关编译标志-fstrict-aliasingGCC在线文档中发现了令人困惑的信息.它说:

-fstrict走样

允许编译器采用适用于正在编译的语言的最严格的别名规则.对于C(和C++),这将根据表达式的类型激活优化.特别地,假设一种类型的对象永远不会与不同类型的对象驻留在相同的地址,除非类型几乎相同.例如,a unsigned intcan可以是a int,但不是a void*或a double.字符类型可以别名为任何其他类型.特别注意这样的代码:

union a_union {
  int i;
  double d;
};

int f() {
  union a_union t;
  t.d = 3.0;
  return t.i;
}
Run Code Online (Sandbox Code Playgroud)

从不同的工会成员阅读的做法比最近写的那个(称为"打字式")很常见.即使使用-fstrict-aliasing,只要通过union类型访问内存,就允许类型为punning.因此,上面的代码按预期工作.

这是我认为我从这个例子和我的疑虑中理解的:

1)别名仅适用于相似类型或char

1)的后果:别名 - 正如文字暗示的那样 - 是你有一个值和两个成员来访问它(即相同的字节);

怀疑:当它们具有相同的字节大小时,两种类型是相似的吗?如果没有,什么是类似的类型?

1)对于非相似类型(无论这意味着什么)的后果,别名不起作用;

2)类型双关语是指我们读的不同于我们写的成员; 它是常见的,只要通过union类型访问内存,它就可以正常工作;

怀疑:在类型相似的特定情况下别名是什么类型?

我感到困惑,因为它表示unsigned int和double不相似,所以别名不起作用; 然后在示例中它是int和double之间的别名,它清楚地表明它按预期工作,但称之为类型 - 惩罚:不是因为类型是或不相似,而是因为它是从一个不写的成员读取.但是从一个没有写的成员那里读取的是我所理解的混淆(正如这个词所暗示的那样).我迷路了.

问题: 有人可以澄清别名和类型惩罚之间的区别,这两种技术的用途是如何在GCC中发挥作用的?编译器标志有什么作用?

Pas*_* By 9

别名可以从字面上理解它的含义:当两个不同的表达式引用同一个对象时.类型 - 双关语是"惩罚"一种类型,即将某种类型的对象用作不同类型.

形式上,类型惩罚是未定义的行为,只有少数例外.当你不小心弄乱位时,它通常会发生

int mantissa(float f)
{
    return (int&)f & 0x7FFFFF;    // Accessing a float as if it's an int
}
Run Code Online (Sandbox Code Playgroud)

例外是(简化)

  • 将整数作为未签名/已签名的对应方访问
  • 访问任何东西char,unsigned char或,或std::byte

这称为严格别名规则:编译器可以安全地假设两个不同类型的表达式从不引用同一个对象(除了上面的例外),否则它们会有未定义的行为.这有利于优化,例如

void transform(float* dst, const int* src, int n)
{
    for(int i = 0; i < n; i++)
        dst[i] = src[i];    // Can be unrolled and use vector instructions
                            // If dst and src alias the results would be wrong
}
Run Code Online (Sandbox Code Playgroud)

gcc说的是它放松了一些规则,并允许通过工会打字,即使标准不要求它

union {
    int64_t num;
    struct {
        int32_t hi, lo;
    } parts;
} u = {42};
u.parts.hi = 420;
Run Code Online (Sandbox Code Playgroud)

这是类型双关语gcc保证将起作用.其他情况似乎可行,但有一天可能会被打破.

  • 我认为你的例子失败了,该结构中位字段的布局本身是实现定义的.C中位字段的不良定义是其中一个非常烦人的事情,它可能为时已晚,无法修复.类型双关语是正常的(至少在GCC中),但位字段可能会或可能不会达到预期效果. (2认同)
  • @PasserBy一个常见的例子就像`union {long long x; struct {unsigned low,high}}`(或者同样,但是使用`unsigned [2]`,你得到了这个想法). (2认同)

n. *_* m. 5

术语是个好东西,我可以随心所欲地使用它,其他人也可以!

当两种类型具有相同的字节大小时,它们是否相似?如果不是,类似的类型有哪些?

粗略地说,当类型的常量或符号不同时,它们是相似的。仅以字节为单位的大小绝对是不够的。

别名是类型相似的类型双关的特定情况吗?

类型双关是任何规避类型系统的技术。

别名是一种特殊情况,涉及将不同类型的对象放置在同一地址处。当类型相似时通常允许使用别名,否则禁止使用别名。此外,可以通过左值char(或类似的char)访问任何类型的对象,但执行相反的操作(即访问类型的对象char)左值访问任何类型的对象,但不允许C 和 C++ 标准都保证了这一点,GCC 只是实现了标准的要求。

GCC 文档似乎在狭义上使用“类型双关语”,即读取除最后写入的联合成员之外的联合成员。即使类型不相似,C 标准也允许这种类型双关。OTOH C++ 标准不允许这样做。GCC 可能会也可能不会将权限扩展到 C++,文档对此并不清楚。

如果没有-fstrict-aliasing,GCC 显然会放松这些要求,但尚不清楚具体放松到什么程度。请注意,这-fstrict-aliasing是执行优化构建时的默认设置。

最重要的是,只需按照标准进行编程即可。如果 GCC 放宽了标准的要求,那么影响并不大,也不值得这么麻烦。