是将零添加到空指针的未定义行为,编译器错误或其他错误的失败测试吗?

时间:2018-10-15 00:40:02

标签: c++ c++14 language-lawyer undefined-behavior compiler-bug

我为C ++ 14项目编写了一个轻量级的string_view包装器,在MSVC 2017中,它在编译时触发了static_assert,但在运行时传递了相同的代码常规assert。我的问题是,这是编译器错误,明显的未定义行为还是其他所有原因?

以下是摘要代码:

#include <cassert> // assert
#include <cstddef> // size_t

class String_View
{
    char const* m_data;
    std::size_t m_size;
public:
    constexpr String_View()
      : m_data( nullptr ),
        m_size( 0u )
    {}

    constexpr char const* begin() const noexcept
    { return m_data; }
    constexpr char const* end() const noexcept
    { return m_data + m_size; }
};

void static_foo()
{
    constexpr String_View sv;

//    static_assert( sv.begin() == sv.end() ); // this errors
    static_assert( sv.begin() == nullptr );
//    static_assert( sv.end() == nullptr ); // this errors
}

void dynamic_foo()
{
    String_View const sv;

    assert( sv.begin() == sv.end() ); // this compiles & is optimized away
    assert( sv.begin() == nullptr );
    assert( sv.end() == nullptr ); // this compiles & is optimized away
}

这是我用来复制问题的Compiler Explorer link

据我所知,从任何指针值中添加或减去0始终是有效的:

解决方法:

如果我将end方法更改为以下方法,则失败的static_assert将通过。

constexpr char const* end() const noexcept
{ return ( m_data == nullptr
           ? m_data
           : m_data + m_size ); }

修补:

在评估m_data + m_size之前,我认为表达式m_size == 0本身就是UB。但是,如果我用荒谬的end代替return m_data + 0;的实现,仍然会产生两个static_assert错误。 :-/

更新

这似乎是一个已在15.7和15.8之间修复的编译器错误。

2 个答案:

答案 0 :(得分:13)

这看起来像是MSVC错误,C ++ 14草案标准明确允许从[expr.add]p7到指针的值进行加法和减法,以将其与CWG defect 1776进行比较:

  

如果将值0添加到指针值或从指针值中减去,结果将等于原始指针值。如果两个指针指向同一对象,或者两个指针都指向同一数组的末尾,或者两个指针都为空,并且两个指针相减,则结果等于等于转换为std :: ptrdiff_t类型的值0。 >

似乎[expr.add]p7导致p0137调整了[expr.add]p4以明确说出0

最新草案使这一点更加明确this github issue

  

将具有整数类型的表达式J添加到指针类型的表达式P或从中减去时,结果的类型为P。
  -如果P的计算结果为空指针值,而J的计算结果为0,则结果为空指针值。
  -否则,如果P指向具有n个元素的数组对象x的元素x [i],85则表达式P + J和J + P(其中J的值为j)指向元素x [(可能是假设的)]。 i + j]如果0≤i+j≤n,并且表达式P-J指向(可能是假设的)元素x [ij](如果0≤i-j≤n)。   (4.3)。
  -否则,行为是不确定的。

对此更改进行了编辑,请参见this PRundefined behavior in a constant expression is ill-formed

MSVC在这里是不一致的,因为它允许像gcc和clang一样在常量表达式中添加和减去零。这很关键,因为live godbolt example因此需要诊断。给出以下内容:

null pointer

gcc,clang和MSVC允许它使用常量表达式(live godbolt),尽管遗憾的是,MSVC在允许以下值方面也不一致,因为它也允许非零值:

constexpr int *p = nullptr  ;
constexpr int z = 0 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;

clang和gcc都说它格式错误,而MSVC却没有({{3}})。

答案 1 :(得分:12)

我认为这绝对是MSVC评估常量表达式的方式中的一个错误,因为GCC和Clang的代码没有问题,并且标准很明显,将0赋给null指针会产生null指针([expr。添加] / 7)。