web-dev-qa-db-ja.com

nullポインターの未定義の動作にゼロを追加するこの失敗したテスト、コンパイラのバグ、または他の何かですか?

C++ 14プロジェクト用の軽量のstring_viewラッパーを作成しました。MSVC2017では、コンパイル時に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
}

これは、問題を再現するために使用した コンパイラエクスプローラーリンク です。

私が知ることができることから、ポインタ値から0を加算または減算することは常に有効です:

回避策:

endメソッドを次のように変更すると、失敗したstatic_assertsは成功します。

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;に置き換えると、2つのstatic_assertエラーが生成されます。 :-/

更新:

これは、15.7〜15.8の間に修正されたコンパイラのバグのようです。

24

これは、M ++のバグのように見えます。C++ 14ドラフト標準では、値_0_をポインターに明示的に加算および減算して、 [expr.add] p7 からそれ自体と比較できます。

値0がポインター値に加算または減算されると、結果は元のポインター値と等しくなります。 2つのポインターが同じオブジェクトを指しているか、両方が同じ配列の終わりを過ぎているか、両方がヌルであり、2つのポインターが減算される場合、結果はstd :: ptrdiff_t型に変換された値0と等しくなります。

CWG欠陥1776 p0137に至り、 [expr.add] p7 を明示的に_null pointer_と言うように調整します。

最新のドラフトでは、これがさらに明確になりました [expr.add] p4

整数型の式Jがポ​​インター型の式Pに加算または減算されると、結果の型はPになります。
-PがNULLポインター値に評価され、Jが0に評価される場合、結果はNULLポインター値になります。
-それ以外の場合、Pがn個の要素を持つ配列オブジェクトxの要素x [i]を指している場合85、式P + JおよびJ + P(Jは値jを持っています)は(おそらく-仮定)0≤i+j≤nの場合は要素x [i + j]であり、式P-Jは0≤i-j≤nの場合(おそらく仮説的な)要素x [i-j]を指します。 (4.3)。
-それ以外の場合、動作は未定義です。

この変更は、編集的に this github issue および this PR を参照して行われました。

MSVCは、gccやclangと同様に、定数式にゼロを追加および減算できるという点で一貫性がありません。 定数式の未定義の動作が不正な形式 であり、診断が必要なため、これが重要です。次の場合:

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

gcc、clang、およびMSVCでは、定数式( live godbolt example )を使用できますが、悲しいことに、MSVCは次の場合にゼロ以外の値も許可するという点で二重に矛盾しています。

_constexpr int *p = nullptr  ;
constexpr int z = 1 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;
_

clangとgccはどちらも不正な形式であると言いますが、MSVCはそうではありません( live godbolt )。

13
Shafik Yaghmour

これは間違いなく、MSVCが定数式を評価する方法のバグだと思います。GCCとClangにはコードに関する問題がなく、標準では、nullポインターに0を追加するとnullポインター([expr.add]/7)。

12
Brian