【问题标题】:When CAN i break aliasing rules?我什么时候可以打破别名规则?
【发布时间】:2012-04-04 07:17:39
【问题描述】:

我收到此警告。我想要定义的行为,但我想保持此代码不变。我什么时候可以打破别名规则?

警告:取消引用类型双关指针将破坏严格别名规则 [-Wstrict-aliasing]

String 是我自己的字符串,它是一个 POD。这段代码是从 C 调用的。S 可能是一个 int。字符串几乎是struct String { RealString*s; },但是模板化和辅助函数。我做了一个静态断言来确保 String 是一个 pod,是 4bytes 并且 int 是 4bytes。我还写了一个断言来检查所有指针是否>= NotAPtr。它在我的 new/malloc 重载中。如果您建议,我也可以将该断言放入 String 中

考虑到我遵循的规则(主要是字符串是一个 pod,并且总是与 int 大小相同),如果我打破别名规则会很好吗?这是少数几次打破它的情况之一吗?

void func(String s) {
    auto v=*(unsigned int*)&s;
    myassert(v);
    if(v < NotAPtr) {
        //v is an int
    }
    else{
        //v is a ptr
    }
}

【问题讨论】:

  • “我能开多快而不拿到超速罚单?”微软对MAKEINTRESOURCE 做了类似的事情,但他们也编写了编译器并且可以添加扩展。普通人做不到。在 C++ 中,他们可以添加一个重载。
  • 你在滑坡上。你真的有充分的理由不制作单独的 func_i 和 func_s 函数吗?
  • @PerJohansson:我在项目开始时做过。现在这个理由已经不存在了。所以我做了2个函数。 +1
  • “破坏别名规则”永远不会“很好”,因为它是 UB,它为编译器提供了一张免费票,可以删除或呈现无法识别的整个程序。我们是否可以看到它并说“是的,这将在 99% 的带有非反复无常编译器的平台上工作”并不重要;标准不要求存在这样的编译器。

标签: c++ strict-aliasing type-punning


【解决方案1】:

memcpy 完全支持。双关语也是char*(例如,您可以使用std::copy)。

【讨论】:

  • 我不确定 C++,但可以肯定的是,在 C99 中,memcpy 函数只有在目标操作数具有声明类型的情况下才能保证类型转换是安全的。规范经历了很多麻烦,以防止 memcpy 在没有声明类型的情况下安全地用于将一种类型的对象复制到存储中,以便将其作为不同类型进行访问。
  • @supercat:我看到string.h 具有以下规则,因此适用于memcpy——“除非在本子条款中对特定函数的描述中另有明确说明,否则此类指针参数调用仍应具有有效值,如 7.1.4 中所述。”您认为 7.1.4 中究竟有什么问题?首先,您将如何获得“没有声明类型的存储”?为什么这对于所描述的场景(int i; memcpy(&amp;i, &amp;s, sizeof i);)很重要?
  • 在 C99 中(我认为这在 C11 中没有改变),鉴于 int x=1234; long *p = malloc(sizeof (long)); *p = 5678; memcpy(p, &amp;x, sizeof x;); 尝试将 *p 读取为 long 将调用未定义行为,即使 int 和 @987654333 @ 具有相同的表示形式,因为 memcpy 会将 *p 的有效类型设置为 int
  • 基本问题是,为了获得良好的性能,编译器需要知道 memcpy 和 memmove 的源或目标可能会产生哪些别名,但这些函数是在编译器不关心的时候定义的;因此,它们没有提供指示类型的方法。这个问题可以通过要求这些函数假定源和目标上的最坏情况别名来解决,但添加新函数让程序员指定源和/或目标类型是否已知,或者目标类型是否已知匹配未知来源类型。
  • 相反,标准的作者提出了memcpy/memmove,以便在源类型已知而目标类型未知的情况下,编译器可能会假设目标将被读取使用与之前编写源代码相同的类型。这在假设正确的情况下可能会有所帮助,但考虑到 memcpy 的主要用途之一历来是执行类型双关语,让编译器做出一个糟糕的假设。跨度>
【解决方案2】:

如果您不能按照建议将代码更改为 2 个函数,为什么不(需要 C99 编译器作为使用 uintptr_t - 对于较旧的 MSVC,您需要自己定义,2008/2010 应该可以):

void f(RealString *s) {
    uintptr_t int = reinterpret_cast<uintptr_t>(s);
    assert(int);
}

【讨论】:

    【解决方案3】:

    标准规定了所有符合要求的实现必须以可预测的方式处理的最小操作集,除非它们遇到翻译限制(因此所有赌注都被取消)。它并不试图定义实现必须支持以适合任何特定目的的所有操作。相反,对超出授权范围的行动的支持被视为实施质量问题。作者承认,一个实现可能是符合标准的,但质量却很差,以至于毫无用处。

    像您这样的代码应该可用于低级编程的高质量实现,并以预期的方式表示内存中的事物。不应期望它可用于其他类型的实施,包括那些将“实施质量”问题解释为试图以低质量但符合标准的方式行事的邀请。

    【讨论】:

      【解决方案4】:

      将变量视为两种不同类型的安全方法是将其转换为联合。联合的一部分可以是你的指针,另一部分是整数。

      struct String
      {
          union
          {
              RealString*s;
              int i;
          };
      };
      

      【讨论】:

      • 从技术上讲,联合可能不适用于类型双关语,并且如果从 i 读取的 s 存在未定义的存储(例如,它可以始终为 0)。然而,在这种情况下,由于奇怪的转换,存在严格的别名违规 - 不是由于任何“真实”违规,因此可以简单地从指针转换为 int。
      • @BenVoigt:实际上,从union 的成员读取而不是最后写入的是未指定行为,而不是未定义
      • @james:你能指出标准中的部分吗?据我所知,读取联合的非活动成员涉及(1)一个成员访问表达式,它是一个左值,以及(2)一个左值到右值的转换。由于左值引用的对象不是左值类型的对象(也不是任何子类),因此任何此类程序都会根据第 4.1 节([conv.lval])调用未定义的行为。
      • @BenVoigt 您需要记住这是读取指针而不是取消引用指针。
      • @acidzombie24:成员访问也是一个左值。在String t; t.s = new RealString("literal"); auto v = t.i; 中,最后一条语句需要左值到右值的转换,使用与实际存在的对象类型(RealString*)不同的类型(int)。
      猜你喜欢
      • 2011-01-05
      • 1970-01-01
      • 2011-03-03
      • 1970-01-01
      • 1970-01-01
      • 2011-02-24
      • 1970-01-01
      • 2020-05-18
      • 1970-01-01
      相关资源
      最近更新 更多