这个失败的测试是否为空指针未定义的行为,编译器错误或其他东西增加零?

Cha*_*cox 24 c++ undefined-behavior language-lawyer compiler-bug c++14

string_view为C++ 14项目编写了一个轻量级的包装器,并且在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
}
Run Code Online (Sandbox Code Playgroud)

这是我用来复制问题的编译器资源管理器链接.

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

解决方法:

如果我将我的end方法更改为以下内容,则失败的static_asserts将通过.

constexpr char const* end() const noexcept
{ return ( m_data == nullptr
           ? m_data
           : m_data + m_size ); }
Run Code Online (Sandbox Code Playgroud)

修修补补:

我想也许表达m_data + m_size本身就是UB,在m_size == 0被评估的事实之前.然而,如果我用end无意义的替换实现return m_data + 0;,这仍然会产生两个static_assert错误.: - /

更新:

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

Sha*_*our 13

这看起来像一个MSVC错误,C++ 14草案标准明确允许0[expr.add] p7向比较等于自身的指针添加和减去该值:

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

它看起来像CWG缺陷1776导致p0137调整[expr.add] p7明确说null pointer.

最新的草案使这更加明确[expr.add] p4:

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

这个改变是在编辑上看到这个github问题这个PR.

MSVC在这里是不一致的,因为它允许在常量表达式中添加和减去零,就像gcc和clang一样.这是关键,因为常量表达式中的未定义行为是不正确的,因此需要诊断.鉴于以下内容:

constexpr int *p = nullptr  ;
constexpr int z = 0 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;
Run Code Online (Sandbox Code Playgroud)

GCC,铛和MSVC允许它的常量表达式(活godbolt例子)虽然可悲MSVC是因为它允许非零值,以及,给出下面的双重不一致:

constexpr int *p = nullptr  ;
constexpr int z = 1 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;
Run Code Online (Sandbox Code Playgroud)

clang和gcc都表示它是不正确的,而MSVC则没有(活神开玩笑).


Bri*_*ian 11

我认为这肯定是MSVC评估常量表达式的方式中的一个错误,因为GCC和Clang对代码没有任何问题,并且标准很清楚,向空指针添加0会产生空指针([expr.add]/7).