c++ - punteros - ¿Es esta prueba fallida que agrega cero a un puntero nulo comportamiento indefinido, un error del compilador o algo más?
punteros en c (2)
Escribí un string_view
ligero para un proyecto de C ++ 14, y con MSVC 2017 está activando un static_assert
en tiempo de compilación, pero el mismo código en tiempo de ejecución pasa la assert
regular. Mi pregunta es: ¿es esto un error de compilación, un comportamiento manifiesto indefinido o algo completamente distinto?
Aquí está el código destilado:
#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
}
Aquí hay un enlace de Compiler Explorer que usé para replicar el problema.
De lo que puedo decir, agregar o restar 0
de cualquier valor de puntero siempre es válido:
- c ++ - ¿Se ha definido el comportamiento de restar dos punteros NULL? - Stack Overflow , ultima blockquote
- Operadores aditivos - cppreference.com , el último punto de la última lista de viñetas
- libstdc ++: string_view Archivo de origen , implementación de
end()
etc.
Solución:
Si cambio mi método end
a lo siguiente, los static_assert
s que static_assert
pasarán.
constexpr char const* end() const noexcept
{ return ( m_data == nullptr
? m_data
: m_data + m_size ); }
Jugueteando
Pensé que tal vez la expresión m_data + m_size
sí es UB, antes de que se m_size == 0
. Sin embargo, si sustituyo la implementación de end
con el return m_data + 0;
sin sentido return m_data + 0;
, esto sigue generando los dos errores static_assert
. : - /
Actualizar:
Esto parece ser un error de compilación que se corrigió entre 15.7 y 15.8.
Creo que este es definitivamente un error en la forma en que MSVC evalúa expresiones constantes, ya que GCC y Clang no tienen problemas con el código, y el estándar es claro que agregar 0 a un puntero nulo produce un puntero nulo ([expr.add] / 7).
Esto parece un error de MSVC que el borrador estándar de C ++ 14 permite explícitamente sumar y restar del valor 0
a un puntero para comparar igual a sí mismo, de [expr.add]p7 :
Si el valor 0 se agrega o se resta de un valor de puntero, el resultado se compara igual al valor de puntero original. Si dos punteros apuntan al mismo objeto o ambos apuntan uno más allá del final de la misma matriz o ambos son nulos, y los dos punteros se restan, el resultado se compara con el valor 0 convertido al tipo std :: ptrdiff_t.
Parece que el defecto 1776 de CWG condujo a p0137 que ajustó [expr.add]p7 para decir explícitamente el null pointer
.
El último borrador lo hizo aún más explícito [expr.add]p4 :
Cuando una expresión J que tiene un tipo integral se agrega o se resta de una expresión P de tipo puntero, el resultado tiene el tipo de P.
- Si P se evalúa como un valor de puntero nulo y J se evalúa como 0, el resultado es un valor de puntero nulo.
- De lo contrario, si P apunta al elemento x [i] de un objeto de matriz x con n elementos, 85 las expresiones P + J y J + P (donde J tiene el valor j) apuntan al elemento (posiblemente hipotético) x [ i + j] si 0≤i + j≤n y la expresión P - J apunta al elemento (posiblemente hipotético) x [i − j] si 0≤i − j≤n. (4.3).
- De lo contrario, el comportamiento es indefinido.
Este cambio se hizo editorialmente ver este tema github y este PR .
MSVC es incoherente aquí, ya que permite sumar y restar cero en una expresión constante como lo hacen gcc y clang. Esto es clave porque el comportamiento indefinido en una expresión constante está mal formado y, por lo tanto, requiere un diagnóstico. Dado lo siguiente:
constexpr int *p = nullptr ;
constexpr int z = 0 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;
gcc, clang y MSVC le permiten una expresión constante ( ejemplo de live godbolt ), aunque lamentablemente MSVC es doblemente inconsistente ya que también permite valores no cero, dado lo siguiente:
constexpr int *p = nullptr ;
constexpr int z = 1 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;
tanto clang como gcc dicen que está mal formado, mientras que MSVC no lo hace ( live godbolt ).