¿Es el comportamiento indefinido de sizeof (* ptr) cuando se apunta a memoria no válida?

Todos sabemos que desreferenciar un puntero nulo o un puntero a la memoria no asignada invoca un comportamiento indefinido.

Pero, ¿cuál es la regla cuando se usa dentro de una expresión pasada a sizeof ?

Por ejemplo:

 int *ptr = 0; int size = sizeof(*ptr); 

¿Esto tampoco está definido?

En la mayoría de los casos, encontrará que sizeof(*x) realidad no evalúa *x en absoluto. Y, dado que es la evaluación (desreferenciación) de un puntero que invoca un comportamiento indefinido, encontrará que está más que correcto. El estándar C11 tiene esto que decir en 6.5.3.4. The sizeof operator /2 6.5.3.4. The sizeof operator /2 (mi énfasis en todas estas citas):

El operador sizeof produce el tamaño (en bytes) de su operando, que puede ser una expresión o el nombre entre paréntesis de un tipo. El tamaño se determina a partir del tipo de operando. El resultado es un entero. Si el tipo del operando es un tipo de matriz de longitud variable, se evalúa el operando; de lo contrario, el operando no se evalúa y el resultado es una constante entera.

Esta es la fraseología idéntica a la misma sección en C99. C89 tenía una redacción ligeramente diferente porque, por supuesto, no había VLA en ese punto. De 3.3.3.4. The sizeof operator 3.3.3.4. The sizeof operator :

El operador sizeof produce el tamaño (en bytes) de su operando, que puede ser una expresión o el nombre entre paréntesis de un tipo. El tamaño se determina a partir del tipo del operando, que no se evalúa por sí mismo. El resultado es una constante entera.

Entonces, en C, para todos los no VLA, no tiene lugar la desreferenciación y la statement está bien definida. Si el tipo de *x es un VLA, se considera un tamaño de fase de ejecución, algo que debe resolverse mientras el código se está ejecutando; todos los demás se pueden calcular en tiempo de comstackción. Si x sí es el VLA, es lo mismo que en los otros casos, no se realiza ninguna evaluación cuando se usa *x como argumento para sizeof() .


C ++ tiene (como se esperaba, ya que es un lenguaje diferente) reglas ligeramente diferentes, como se muestra en las diversas iteraciones de la norma:

Primero, C++03 5.3.3. Sizeof /1 C++03 5.3.3. Sizeof /1 :

El operador sizeof produce el número de bytes en la representación del objeto de su operando. El operando es una expresión que no se evalúa o un ID de tipo entre paréntesis.

En, C++11 5.3.3. Sizeof /1 C++11 5.3.3. Sizeof /1 , encontrará una redacción ligeramente diferente pero el mismo efecto:

El operador sizeof produce el número de bytes en la representación del objeto de su operando. El operando es una expresión, que es un operando no evaluado (Cláusula 5), ​​o un ID de tipo entre paréntesis.

C++11 5. Expressions /7 (la cláusula 5 mencionada anteriormente) define el término “operando no evaluado” como quizás una de las frases más inútiles y redundantes que he leído durante un tiempo, pero no sé qué estaba pasando. a través de la mente de las personas ISO cuando lo escribieron:

En algunos contextos ( [algunas referencias a secciones que detallan esos contextos – pax] ), aparecen operandos no evaluados. Un operando no evaluado no se evalúa.

C ++ 14/17 tiene la misma redacción que C ++ 11, pero no necesariamente en las mismas secciones, ya que se agregaron cosas antes de las partes relevantes. Están en 5.3.3. Sizeof /1 5.3.3. Sizeof /1 y 5. Expressions /8 para C ++ 14 y 8.3.3. Sizeof /1 8.3.3. Sizeof /1 y 8. Expressions /8 para C ++ 17.

Por lo tanto, en C ++, la evaluación de *x en sizeof(*x) nunca se lleva a cabo, por lo que está bien definida, siempre que siga todas las otras reglas, como proporcionar un tipo completo, por ejemplo. Pero, la conclusión es que no se realiza la eliminación de referencias , lo que significa que no causa un problema.

En realidad, puede ver esta no evaluación en el siguiente progtwig:

 #include  #include  int main() { int x = 42; std::cout << x << '\n'; std::cout << sizeof(x = 6) << '\n'; std::cout << sizeof(x++) << '\n'; std::cout << sizeof(x = 15 * x * x + 7 * x - 12) << '\n'; std::cout << sizeof(x += sqrt(4.0)) << '\n'; std::cout << x << '\n'; } 

Se podría pensar que la línea final arrojaría algo muy diferente a 42 ( 774 , basado en mis cálculos aproximados) porque x se ha cambiado bastante. Pero ese no es el caso, ya que es solo el tipo de expresión en sizeof que importa aquí, y el tipo se reduce al tipo x .

Lo que ves (aparte de la posibilidad de diferentes tamaños de puntero en líneas que no sean la primera y la última) es:

 42 4 4 4 4 42 

No. sizeof es un operador y funciona con tipos, no con el valor real (que no se evalúa).

Para recordarle que es un operador, le sugiero que adquiera el hábito de omitir los corchetes cuando sea práctico.

 int* ptr = 0; size_t size = sizeof *ptr; size = sizeof (int); /* brackets still required when naming a type */ 

La respuesta puede ser diferente para C, donde sizeof no es necesariamente una construcción en tiempo de comstackción, pero en C ++ la expresión proporcionada a sizeof nunca se evalúa. Como tal, nunca existe la posibilidad de que un comportamiento indefinido se exhiba. Con una lógica similar, también puede “llamar” funciones que nunca están definidas [porque la función nunca se llama en realidad, no es necesaria ninguna definición], un hecho que se usa con frecuencia en las reglas de SFINAE.

sizeof y decltype no evalúan sus operandos, solo tipos de computación.

sizeof(*ptr) es lo mismo que sizeof(int) en este caso.

Como sizeof no evalúa su operando, en la expresión sizeof (*ptr) , ptr no se evalúa, por lo tanto, no se desreferencia. El operador sizeof solo necesita determinar el tipo de expresión *ptr para obtener el tamaño apropiado.