这个失败的测试是将零添加到空指针未定义的行为、编译器错误还是其他什么?
Is this failing test that adds zero to a null pointer undefined behaviour, a compiler bug, or something else?
我为 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
}
这是我用来复制问题的编译器资源管理器链接。
据我所知,从任何指针值中添加或减去0
始终有效:
- c++ - 是否定义了减去两个 NULL 指针的行为? - 堆栈溢出,最后一个块引用
- 添加剂运算符 - cppreference.com,最后一个项目符号列表的最后一个项目符号
- libstdc++:string_view源文件,
end()
的实现等。
解决方法:
如果我将end
方法更改为以下内容,则失败的static_assert
将通过。
constexpr char const* end() const noexcept
{ return ( m_data == nullptr
? m_data
: m_data + m_size ); }
修补:
我想也许表达式本身m_data + m_size
是 UB,在评估m_size == 0
之前。 然而,如果我用无意义的return m_data + 0;
替换end
的实现,这仍然会产生两个static_assert
错误。 :-/
更新:
这似乎确实是一个在 15.7 和 15.8 之间修复的编译器错误。
这看起来像一个MSVC错误 C++14 草案标准明确允许在指针0
添加和减去值以从 [expr.add]p7 中等于自身进行比较:
中添加或减去值 0,则结果将等于原始指针值。如果两个指针指向同一对象,或者两个指针都指向同一数组末尾的一个指针,或者两个指针都为 null,并且减去这两个指针,则结果将等于转换为类型 std::p trdiff_t 的值 0。
看起来 CWG 缺陷 1776 导致 p0137 调整 [expr.add]p7 以明确表示null pointer
。
最新的草案对此更加明确[expr.add]p4:
当将整型表达式 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[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;
gcc,clang和MSVC允许它一个常量表达式(实时Godbolt示例(,尽管遗憾的是MSVC是双重不一致的,因为它也允许非零值,给定以下内容:
constexpr int *p = nullptr ;
constexpr int z = 1 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;
Clang和GCC都说它是畸形的,而MSVC则没有(Live Godbolt(。
我认为这绝对是 MSVC 计算常量表达式方式中的一个错误,因为 GCC 和 Clang 对代码没有问题,并且标准很清楚,将 0 添加到空指针会产生空指针 ([expr.add]/7(。
- MSVC多行宏编译器错误
- 静态数据成员的问题-修复链接错误会导致编译器错误
- C++,我收到一个无法理解的编译器错误
- 我收到同义重复编译器错误。我应该如何修复"类型"X"的参数与类型"X"的参数不兼容?
- 重载方法的方式会在使用临时调用时生成编译器错误
- 尝试使用继承和模板实现CRTP.Visual Studio正在生成编译器错误
- 编译器错误:destuctor 的更宽松的抛出说明符
- Android NDK clang 编译器错误在 Windows 上显示'No such file or directory'
- C++ G++ 编译器 - 错误:隐式声明的定义
- 这个失败的测试是将零添加到空指针未定义的行为、编译器错误还是其他什么?
- 模板模板参数和模板别名:编译器错误?
- C++ 编译器错误:P1LinkedList.cpp:145:错误:重载的"to_string(int&)"调用不明确
- 在gcc中意外调用了Const重载.编译器错误或兼容性修复程序
- C1001内部编译器错误是由于矢量初始化(如数组)引起的
- 编译器错误(英特尔并行工作室 2019 与 Visual Studio 社区 2019)
- 如何在C++中克服此 C4430 编译器错误?
- 尝试使用 WinBGI 显示文本时出现编译器错误
- 使用typedef'ed返回类型声明友元函数时出现编译器错误
- 如何在类模板的成员函数中正确调用函数对象?正在生成 Visual Studio 编译器错误 C2440
- C++自定义分配器大小参数作为模板参数会引发编译器错误