Clang严格混叠优化与无法访问的代码违反了严格混叠

时间:2019-03-20 15:14:09

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

我有一个关于严格混叠和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编译给出不同的结果?

2 个答案:

答案 0 :(得分:2)

表达式语句

                    *(long long *)grp = 0;
正如您在问题中所观察到的,

由于通过不同的不兼容类型(int[4])的左值访问类型long long的对象而具有未定义的行为-严格混叠。但这并不仅限于 runtime 行为。 (潜在的)问题在翻译时可见,翻译时行为也未定义,因此每次执行的结果也是如此。

至少,这至少是一些编译器开发人员所遵循的标准的解释。这里周围的一些人反对这样的解释,但这并没有改变您必须处理它们的事实。

关于更新

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

  • 明确允许将一种对象指针类型的值转换为另一种对象指针类型。关于结果对齐有一些警告,但C要求它始终能够工作以转换为char *,并且它需要反向转换以重现原始指针值(如果以开头有效,则没有对齐问题。 )。

  • 允许通过字符类型的左值访问任何对象的表示形式。换句话说,允许char *别名任何对象的任何部分,因此即使您不要直接通过传递给{{1 }},合格的编译器必须假定这些指针可以为程序中的任何对象起别名。

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

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

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

答案 1 :(得分:0)

谢谢大家的评论!您已经帮助我更好地理解了问题。

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

再次感谢!