sirven que punteros puntero para operadores los lenguaje direccion declaracion cadenas aritmetica c++ c++14 language-lawyer undefined-behavior compiler-bug

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:

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 ).