【问题标题】:Clang strict-aliasing optimizations vs unreachable code violating strict-aliasingClang 严格混叠优化与违反严格混叠的无法访问代码
【发布时间】:2019-03-20 15:14:09
【问题描述】:

我有一个关于严格别名和 clang 优化的问题,例如。

让我们考虑下面的例子(1):

typedef void (*FTy)(void);
FTy F = 0;
(*F)();

这是一种未定义的行为。

让我们考虑下面的例子(2):

typedef void (*FTy)(void);
static const FTy F = 0;

void g( int flag)
{
    if ( flag )
    {
        (*F)();
    }
}

int main( void)
{
    g( 0);
    return (0);
}

F 的间接调用仍然是“未定义的行为”,但始终处于错误状态。所以程序一定是正确的。

现在让我们考虑主要示例 (3): (第二版:感谢@Antti Haapala 简化版) (第三版:always_inline的使用)

#include <stdio.h>

#ifndef BUGGY
#define BUGGY 1
#endif

static inline void __attribute__((always_inline)) longLongAssign( int cond, char *ptr)
{
    if ( cond )
    {
        *((long long *)ptr) = 0;
    }
}

void funcA(int s, int g)
{
    int i, j = 0, k;
    int grp[4] = {-1, -1};
    void *ptr[2] = {(void *)&(grp[0]), 0};

    for (i = 0, k = 0; i < 1; ++i) {
        for (j = 0; j < 1; ++j) {
            if ( grp[g] > 0 )
            {
                if ( g > 5 )
                {
                    continue;

                } else
                {
                    longLongAssign( g > 3, (char *)ptr[0]);
                }
            }
            grp[k++] = 0;
        }
        printf("this should be zero: %d\n", grp[0]);
    }
}

int main(void) {
    funcA(0, 1);
}

gcc编译并执行

this should be zero: 0

通过“clang-7.0 -O0”编译并执行

this should be zero: 0

通过“clang-7.0 -O1 -fno-strict-aliasing”编译并执行

this should be zero: 0

通过“clang-7.0 -O1”编译并执行

this should be zero: -1

在主要示例中,grp 的商店之一正式违反严格混叠

        *((long long *)ptr) = 0;

但是这家商店总是处于虚假状态。

这里的问题是:商店如何运作

  • 违反了严格的别名规则
  • 但位于无法访问的语句中

可能会影响程序执行的任何方式?

C 语言标准是否正确?

下面的示例 (4) 是否正确、定义明确并且没有未定义的行为?

void assign( int type, char *ptr)
{
    if ( ptr )
    {
        if ( (type == 0) )
        {
            *((int *)ptr) = 1;

        } else if ( (type == 1) )
        {
            *((float *)ptr) = 1;
        } else
        {
            // unknown type
        }
     }
 }

 int main( void)
 {
     int a;
     float b;

     assign( 0, (char *)&a);
     assign( 1, (char *)&b);
     assign( 0, (char *)0);

     return (0);
 }

函数 main 中的内联和常量传播优化给出

    ...
    if ( &a )
    {
        if ( (0 == 0) )
        {
            *((int *)&a) = 1;

        } else if ( (0 == 1) )
        {
            *((float *)&a) = 1;
        } else
        {
            // unknown type
        }
    }
    ...

单手店铺运营

            *((float *)&a) = 1;

正式违反严格别名,但处于无法访问的位置。

示例(4)可能不正确的原因有哪些? 如果示例(4)是正确的,那么为什么示例(3)通过 clang 编译给出不同的结果?

【问题讨论】:

  • @ensc 所以这不是最小的。我已经有了 clang 6.0.0 的行为并删除了 while 循环、函数 pointer 取消引用等......
  • longjmp 也不需要。
  • @Павел 您是否同意this code 重现了问题 - 它更像是minimal reproducible example,并且更关注实际问题。
  • @JohnBollinger 循环似乎是必不可少的......而且数组需要 4 个元素而不是 2 个......
  • @Павел 但您是否同意它仍然会重现该问题 - 如果您可以使用您的 GCC、Clang 自己验证它,如果可以,请edit 这个问题包含它 - 并且后人,这是作为问题作者应该做的练习。

标签: c optimization clang undefined-behavior strict-aliasing


【解决方案1】:

表达式语句

                    *(long long *)grp = 0;

由于通过不同的、不兼容类型 (long long) 的左值访问类型为 int[4] 的对象而具有未定义的行为——正如您在问题中观察到的那样,这是一种严格的别名违规。但这不必局限于 runtime 行为。 (潜在的)问题在翻译时是可见的,翻译时的行为也是未定义的,因此每次执行的结果也是如此。

或者至少,这是对至少一些编译器开发人员订阅的标准的解释。这里的一些人反对这样的解释,但这并不能改变你必须处理它们的事实。

关于更新

您的示例 (4) 具有完美定义的行为。这里的主要考虑因素是

  • 明确允许将一种对象指针类型的值转换为不同的对象指针类型。关于结果的对齐有一些注意事项,但 C 要求它始终可以转换为char *,并且它需要反向转换来重现原始指针值(如果它一开始就有效,则没有对齐问题)。

  • 允许通过字符类型的左值访问任何对象的表示。换句话说,char * 可以为任何对象的任何部分设置别名,因此即使您直接通过传递给assign()char * 值访问任何内容,这是一个符合要求的编译器必须假定这些指针可以为程序中的任何对象起别名。

  • 任何类型的空指针都可以转换为另一种对象指针类型,从而产生目标类型的空指针。

  • 通过以与函数实现一致的方式将 type 参数用于函数 assign(),程序可确保最终(仅)通过其正确类型的左值访问每个涉及的对象。

编译器可能应用的优化与此分析无关。它是您提供给编译器的代码,如果定义了行为,则通过该代码来建立行为。假设程序已经定义了行为,编译器有责任确保程序通过翻译成可执行文件而表现出该行为,并且它可以并且确实使用其关于自己的实现的知识来为此提供。

【讨论】:

  • 添加了“问题在翻译时可见”的示例 (4)。 @JohnBollinger,你能评论一下这个例子吗?
  • 我不清楚为什么示例 (4) 很重要,它似乎与其他任何一个都没有有意义的关联。但我已经添加了一些关于它的评论。
  • 通过示例(4)我想在“无法到达的语句位置”中使用“正式违反严格别名”来模拟情况,我想这两个示例都等效于模这个属性。
  • 因此,如果示例(4)有效,则示例(3)也有效。
  • 我将示例(3)修改为使用 longLongAssign 函数。
【解决方案2】:

感谢大家的cmets!你帮助我更好地理解了这个问题。

只是为了澄清我为什么这样做以及我真正想要什么: 我正在某些特定平台上移植 clang。所以我的目标是了解这个测试(来自我们的 autogen 测试系统)是否包含错误,或者更确切地说它是 clang 编译错误。关于这个讨论的结果,我提交了一个 llvm 错误 (https://bugs.llvm.org/show_bug.cgi?id=41178)。

再次感谢!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-04
    • 2023-03-15
    • 2014-02-08
    相关资源
    最近更新 更多