类型惩罚和严格别名的策略

时间:2017-07-27 05:00:15

标签: c++ c strict-aliasing

我很好奇生产环境中的人是否真的设法用严格的别名编译?看起来避免指针转换对于大型代码库来说是一个梦想。我知道你可以使用char * cast来解决别名问题,但是在我曾经工作的每个大型代码库中总是至少有一个指针转换是非常不可避免的。

编辑:这是一个示例,为问题提供更多上下文:想象一个Vector3类,它内部只有3个浮点数,并提供数学上的声音向量操作,如+, - ,点积等。 ..在这种情况下,您可能希望执行向量操作的操作。例如,您可能希望进行分量乘法。一种方法是实现operator *来执行此操作,但这可能会导致错误(例如,用户意图将向量乘以标量而不是另一个向量),编译器无法捕获这些错误。

一种方法是添加允许成对操作的Float3类。在这种情况下,您可能会使用强制转换来暂时将3个浮点数视为Float3,以便执行您不希望Vector3执行的操作。您如何推荐处理此案例?如果编译器根据它是Vector3还是Float3选择对相同的3个浮点数重新排序操作,这肯定会导致别名问题。我见过类似的情况,其中Vector4由于某种原因暂时被视为Vector3。

有没有优雅的策略来处理这个?使用匿名联盟似乎可能过于冗长。

2 个答案:

答案 0 :(得分:1)

这个答案主要关注C,而不是C ++。使用C ++可能有类似的推理。

首先:当然人们编译符合标准的代码。即使在巨大的代码库中也不难做到这一点。当你已经拥有一个依赖于某些未定义行为的大型代码库并尝试使之符合后,可能会非常困难。

  

似乎避免指针转换对于大型代码库来说是一个很好的梦想。

并非每个指针强制转换都违反了严格的别名。避免使用指针强制转换是一种很好的做法,因为您必须仔细查看它们的使用情况。在您的“大型代码库”中,具有指针强制转换,您必须确保它们是合法的。

  

我知道你可以使用char *强制转换来解决别名问题

不一般!通过类型为char *的指针访问任何对象始终是正确的,但相反的方法不成立,因此您不能仅通过首先转换为char *来替换任意指针,然后转换为无论你想要什么。

类型双关语几乎总是不明智。我知道有两个例外:

  1. 访问对象表示的字节,例如用于序列化,复制或转换字节序。此规则涵盖了允许您通过char *
  2. 访问对象的规则
  3. struct inheritance ,例如你有一些基本结构用于类似形状的对象和专门用于不同目的的具体结构。
  4. 对于第二个用例,您将看到很多不合规的代码。一个着名的例子是BSD套接字API,使用这些结构(以及其他):

    struct sockaddr
    {
        unsigned short sa_family;
        char sa_data[14];
    };
    
    struct sockaddr_in
    {
        short sin_family;
        unsigned short sin_port;
        struct in_addr sin_addr;
        char sin_zero[8];
    };
    

    考虑一个函数返回struct sockaddr *,但你得到的对象实际上是struct sockaddr_in,所以你必须在这里做一个不正确的演员。在正确处理时,它不会在实践中引起问题,因为该对象最初是作为struct sockaddr_in创建的,并且只是使用错误的类型传递给您 - 所有您需要做的就是抛出它并忘记指向错误的类型。但要发现它实际上 struct sockaddr_in,您必须检查sa_family成员,并且此访问严格来说是未定义的行为

    这很不幸,因为有一种兼容的方式来实现类似的东西(当然没有与API中现有的相同功能签名,这只是一个假设的例子):

    struct my_sockaddr
    {
        unsigned short family;
    };
    
    struct my_sockaddr_in
    {
        struct my_sockaddr sockaddr;
        unsigned short port;
        struct in_addr addr;
    };
    

    现在,如果你有一个返回struct my_sockaddr *的函数,它可以指向struct my_sockaddr_in但仍然没有违反任何规则,因为struct my_sockaddr是第一个成员(必须在结构的偏移量0)。在检查sa_family成员并因此确定它是struct my_sockaddr_in之后,您可以安全地进行投射。

    如果你觉得它不是那么好,那么

    struct my_sockaddr_in x;
    

    您必须写x.sockaddr.family才能访问family成员,甚至还有一个解决方案可以让您在不违反标准的情况下更加舒适:

    struct my_sockaddr_in
    {
        union
        {
            struct my_sockaddr sockaddr;
            struct
            {
                unsigned short family;
            };
        };
        unsigned short port;
        struct in_addr addr;
    };
    

    这使用匿名联盟匿名结构的功能。给定struct my_sockaddr_in,您现在可以使用x.family安全地访问地址系列。

    这种用法符合§6.5.2.3,第6节

      

    为了简化工会的使用,我们提出了一项特殊保证:如果工会包含   几个结构共享一个共同的初始序列(见下文),如果是联盟   对象当前包含这些结构中的一个,允许检查公共结构   其中任何一个的初始部分都是完整类型的联盟的声明   是可见的。如果相应的成员,Tw o结构共享公共初始序列   对于一个或多个序列具有兼容类型(并且对于位字段,具有相同的宽度)   初始成员。

    总而言之,我会强烈建议不要使用gcc -fno-strict-aliasing标志之类的内容来代表您编写的任何新代码。它只是阻止了一些优化,并允许您编译依赖未定义行为某些代码。在编写完全符合标准的代码时,您无法获得强有力的保证。

答案 1 :(得分:-1)

是的,我编写符合标准的代码并在生产中使用它。