2010-11-26 11 views
10

Hasta ahora no puedo encontrar la manera de deducir que el siguiente:¿En qué casos exactamente el estándar de C++ dice que desreferenciar un puntero no inicializado es un comportamiento indefinido?

int* ptr; 
*ptr = 0; 

es un comportamiento indefinido.

En primer lugar, hay 5.3.1/1 que indica que * significa indirección que convierte T* en T. Pero esto no dice nada sobre UB.

Luego, a menudo se cita 3.7.3.2/4 diciendo que al usar la función de desasignación en un puntero no nulo, el puntero no es válido y el uso posterior del puntero no válido es UB. Pero en el código anterior no hay nada sobre la desasignación.

¿Cómo se puede deducir UB en el código anterior?

+1

Supongo que proviene de C Standard 6.5.3.2/4 – Chubsdad

+0

¿Qué dice la norma sobre la inicialización y la declaración de punteros? Por lo que sé, la declaración no inicializa el puntero, por lo que podría ser cualquier cosa, asignando un valor a donde apunte podría hacer cualquier cosa. Podría estar equivocado ;-) – Jaydee

+0

¿No es un comportamiento indefinido leer desde cualquier variable no inicializada, puntero o no? Considere que puede estar escribiendo en la dirección apuntada, pero está leyendo * desde * el puntero en el proceso. –

Respuesta

3

No voy a fingir que sé mucho sobre esto, pero algunos compiladores inicializarían el puntero a NULL y desreferenciando un puntero a NULL es UB.

Además, teniendo en cuenta que el puntero no inicializado podría apuntar a cualquier cosa (esto incluye NULL), podría concluir que es UB cuando lo desreferencia.

Una nota en la sección 8.3.2 [dcl.ref]

[Nota: en particular, una referencia nula no puede existir en un programa bien definido, ya que la única manera de crear tales una referencia sería vincularla al "objeto" obtenido por desreferenciando un puntero nulo, que causa un comportamiento indefinido. Como se describe en 9.6, una referencia no puede ligarse directamente a un campo de bits. ]

-ISO/IEC 14882: 1998 (E), el estándar ISO C++, en la sección 8.3.2 [dcl.ref]

creo que debería haber escrito esto como comentario en su lugar, yo' No estoy realmente tan seguro.

+0

No puedo culpar a la lógica :-). –

+1

Las notas no son vinculantes, solo son para información http://stackoverflow.com/questions/4274763/what-does-the-note-in-undefined-behavior-paragraph-in-c-standard-mean/4274836#4274836 – sharptooth

+1

@sharptooth: no son * vinculantes * si contradicen el texto normativo, pero a menos que exista un defecto en el estándar, todo lo que digan sobre el idioma es * verdadero *.No sé de ninguna parte en el estándar que diga que puede desreferenciar un puntero nulo :-) Las notas deberían ser redundantes con otra información presente en otra parte del estándar. Pero la prueba de que son ciertas podría requerir un gran recorrido de secciones, deducciones, etc. Por lo tanto, un programador puede optar por confiar en su verdad, mientras que un implementador podría necesitar saber exactamente por qué son ciertas y, por lo tanto, podrían necesitar el texto normativo –

11

Sección 4.1 se parece a un (énfasis ) candidato:

Un lvalue (3,10) de un de no funcionamiento, el tipo T no-matriz se puede convierte en un valor p. Si T es un tipo incompleto , un programa que necesita esta conversión es mal formado. Si el objeto al que se refiere la lvalue no es un objeto de tipo T y no es un objeto de un tipo derivado de T, o si el objeto es sin inicializar, un programa que hace necesaria esta conversión tiene comportamiento indefinido. Si T es un tipo que no es de clase, el tipo del valor r es la versión cv no calificada de T. De lo contrario, el tipo del valor r es T.

Estoy seguro de que la búsqueda de "uninitial" en la especificación puede encontrar más candidatos.

+2

Aprendí de Johannes/litb el otro día que hay un pequeño defecto en la especificación aquí: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#240. Entonces, debe leer ese párrafo con cierta simpatía por el hecho de que cuando dice "no inicializado", realmente el estándar debería ser más claro sobre los valores no inicializados/indeterminados. En este caso, el objeto ciertamente no está inicializado, por lo que su cita lo cubre. –

+0

¿Un documento de estándares mal redactado ?! ¡Pero eso es imposible! ;) –

+0

@SteveJessop No veo cómo se aplica en este caso, ¿cómo puede haber una conversión de * lvalue a rvalue * cuando tienes '* ptr = 0;'? El resultado de '*' es un * lvalue * y '=' requiere que el operando izquierdo tenga un * valor modificable *. –

3

Para desreferenciar el puntero, debe leer desde la variable de puntero (sin hablar del objeto al que apunta). Leer de una variable no inicializada es un comportamiento indefinido.

Lo que haga con el valor del puntero después de haberlo leído ya no importa, ya sea escribiendo (como en su ejemplo) o leyendo desde el objeto al que apunta.

5

La pregunta del OP es absurda. No hay ningún requisito de que el Estándar diga que ciertos comportamientos no están definidos, y de hecho yo argumentaría que toda esa redacción debe eliminarse del Estándar porque confunde a las personas y hace que el Estándar sea más detallado de lo necesario.

El estándar define cierto comportamiento. La pregunta es, ¿especifica algún comportamiento en este caso? Si no lo hace, el comportamiento no está definido, lo diga o no explícitamente.

De hecho, la especificación de que algunas cosas no están definidas se deja principalmente en la Norma como una herramienta de depuración para los escritores de Estándares, la idea es generar una contradicción si hay un requisito en un lugar que entra en conflicto con una declaración explícita de Comportamiento indefinido en otro: esa es una forma de demostrar un defecto en el Estándar. Sin la declaración explícita de comportamiento indefinido, el comportamiento de prescripción de otra cláusula sería normativo y no cuestionado.

+0

el estándar define ciertos comportamientos, pero * debería * definir, y * tiene *, hasta cierto punto y la mayoría de las veces, definió ciertos comportamientos como indefinidos, especialmente cuando tales comportamientos se encuentran en las áreas morales grises. La pregunta es válida. – PoweredByRice

6

he encontrado la respuesta a esta pregunta es un rincón inesperado de la C++ draft standard, sección 24.2requisitos Iterator, concretamente en la sección 24.2.1En general párrafo y que dicen respectivamente (énfasis mío) :

[...] [Ejemplo: después de la declaración de un sin inicializar puntero x (como se con int * x;), x siempre debe suponerse que tiene un valor singular de un puntero. -finalizar ejemplo] [...] Los valores aderezables siempre son no singulares.

y:

Un iterador es un iterador no válida que puede ser singular.

y la nota 268 dice:

Esta definición se aplica a los punteros, punteros ya son iteradores. El efecto de desreferenciar un iterador que ha sido invalidado no está definido.

A pesar de que parezca que hay cierta controversia sobre whether a null pointer is singular or not y parece que el valor singular término necesita ser adecuadamente definido de una manera más general.

La intención de singular se parece resumirse así en el informe de defectos 278. What does iterator validity mean? en la sección lógica que dice:

¿Por qué decimos "puede ser singular", en lugar de "es singular"? Eso es porque un iterador válido es uno que se sabe que no es. Invalidar un iterador significa cambiarlo de tal manera que ya no se sepa que no es unirular. Un ejemplo: insertar correctamente un elemento en el medio de un vector invalida todos los iteradores que apuntan al vector. Que no significa necesariamente que todos se vuelven singulares.

Así invalidación y ser inicializado may crear un valor que es singular pero ya que no podemos probar que son no singular debemos asumir que son singular.

actualización

Un enfoque de sentido común alternativa sería tener en cuenta que el proyecto de sección estándar 5.3.1operadores unarios párrafo que dice (énfasis mío):

El el operador unario * realiza la indirección: la expresión a la que se aplica debe ser un puntero a un tipo de objeto, o un puntero a un tipo de función y el resultado es un valor-I en referencia al objeto o la función a la que los puntos de expresión. [...]

y si a continuación, vaya a la sección 3.10Lvalues ​​y rvalues ​​ párrafo 1 dice (énfasis mío):

un lvalue (la llamada, históricamente, porque lvalues ​​podrían aparecer en el lado izquierdo de una expresión de asignación) designa una función o un objeto. [...]

pero ptr no será, salvo por casualidad, seleccione un objeto válida.

3

Evaluando un puntero no inicializado provoca un comportamiento indefinido. Como la eliminación de referencias del puntero primero requiere una evaluación, esto implica que la desreferenciación también provoca un comportamiento no definido.

Esto fue cierto tanto en C++ 11 como en C++ 14, aunque la redacción cambió.

En C++ 14 está totalmente cubierto por [dcl.init]/12:

Cuando se obtiene de almacenamiento para un objeto con duración automática o dinámica de almacenamiento, el objeto tiene un valor indeterminado, y si no se efectúa la inicialización para el objeto, ese objeto retiene un valor indeterminado hasta que el valor es reemplazado

Si un valor indeterminado es producido por una evaluación, el comportamiento es indefinido excepto en los siguientes casos:

donde los "siguientes casos" son operaciones particulares sobre unsigned char.


En C++ 11, [conv.lval/2] cubiertos Este bajo el procedimiento de conversión-lvalue-a rvalue (es decir, recuperar el valor del puntero del área de almacenamiento denotado por ptr):

Un glvalue de un tipo T no funcional, no de matriz se puede convertir a un valor pr. Si T es un tipo incompleto, un programa que necesita esta conversión está mal formado. Si el objeto al que se refiere glvalue no es un objeto de tipo T y no es un objeto de un tipo derivado de T, o si el objeto no se inicializa, un programa que necesita esta conversión tiene un comportamiento indefinido.

La pieza en negrita se eliminó para C++ 14 y se reemplazó con el texto adicional en [dcl.init/12].

1

Incluso si el almacenamiento normal de algo en la memoria no tiene "espacio" para trampas o representaciones de trampa, las implementaciones no son necesarias para almacenar variables automáticas de la misma manera que las variables de duración estática, excepto cuando existe la posibilidad el código de usuario puede contener un puntero a ellos en alguna parte. Este comportamiento es más visible con tipos enteros. En un sistema de 32 bits típica, dado el código:

uint16_t foo(void); 
uint16_t bar(void); 
uint16_t blah(uint32_t q) 
{ 
    uint16_t a; 
    if (q & 1) a=foo(); 
    if (q & 2) a=bar(); 
    return a; 
} 
unsigned short test(void) 
{ 
    return blah(65540); 
} 

no sería particularmente sorprendente para test para producir 65.540 a pesar de que el valor está fuera del rango representable de uint16_t, un tipo que no tiene representaciones de trampa. Si una variable local de tipo uint16_t tiene un valor indeterminado, no es necesario que su lectura arroje un valor dentro del rango de uint16_t. Dado que pueden producirse comportamientos inesperados al usar números enteros sin signo de esa manera, no hay ninguna razón para esperar que los punteros no puedan comportarse de forma aún peor.

Cuestiones relacionadas