c - array - sizeof(int)
¿Se evalúa el operando de `sizeof` con un VLA? (3)
De hecho, el Estándar parece implicar que el comportamiento no está definido:
re-cotizando N1570 6.5.3.4/2:
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.
Creo que la redacción de la Norma es confusa: el operando que se evalúa no significa que se evaluará *bar
. Evaluar *bar
no ayuda de ninguna manera a calcular su tamaño. sizeof(*bar)
no necesita ser computado en tiempo de ejecución, pero el código generado para esto no necesita desreferenciar la bar
, es más probable que recupere la información de tamaño de una variable oculta que contiene el resultado del cálculo de tamaño en el momento de La instanciación del bar
.
Un argumento en la sección de comentarios de esta respuesta me impulsó a hacer esta pregunta.
En el siguiente código, la bar
apunta a una matriz de longitud variable, por lo que el sizeof
se determina en tiempo de ejecución en lugar de compilar.
int foo = 100;
double (*bar)[foo];
El argumento era si el uso de sizeof
evalúa su operando cuando el operando es una matriz de longitud variable, lo que hace que el comportamiento sizeof(*bar)
no esté definido cuando la bar
no se inicializa.
¿Es un comportamiento indefinido usar sizeof(*bar)
porque estoy desreferenciado un puntero no inicializado? ¿Se evalúa realmente el operando de sizeof
cuando el tipo es una matriz de longitud variable, o simplemente determina su tipo (cómo suele funcionar sizeof
)?
Edit: Todo el mundo parece estar citando este pasaje del borrador C11. ¿Alguien sabe si esta es la redacción de la norma oficial?
Otras dos respuestas ya han citado N1570 6.5.3.4p2:
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.
Según ese párrafo de la norma, sí, se evalúa el operando de sizeof
.
Voy a argumentar que esto es un defecto en la norma; algo se evalúa en tiempo de ejecución, pero el operando no lo es.
Consideremos un ejemplo más simple:
int len = 100;
double vla[len];
printf("sizeof vla = %zu/n", sizeof vla);
De acuerdo con el estándar, sizeof vla
evalúa la expresión vla
. Pero ¿qué significa eso?
En la mayoría de los contextos, la evaluación de una expresión de matriz produce la dirección del elemento inicial, pero el operador sizeof
es una excepción explícita a eso. Podríamos asumir que evaluar vla
significa acceder a los valores de sus elementos, que tiene un comportamiento indefinido ya que esos elementos no se han inicializado. Pero no hay otro contexto en el que la evaluación de una expresión de matriz acceda a los valores de sus elementos, y no hay necesidad de hacerlo en este caso. (Corrección: si se usa un literal de cadena para inicializar un objeto de matriz, se evalúan los valores de los elementos).
Cuando se ejecuta la declaración de vla
, el compilador creará algunos metadatos anónimos para mantener la longitud de la matriz (debe hacerlo, ya que al asignar un nuevo valor a len
después de definir y asignar vla
la longitud de vla
no se modifica). Todo lo que se debe hacer para determinar el sizeof vla
es multiplicar ese valor almacenado por sizeof (double)
(o simplemente recuperar el valor almacenado si almacena el tamaño en bytes).
sizeof
también se puede aplicar a un nombre de tipo entre paréntesis:
int len = 100;
printf("sizeof (double[len]) = %zu/n", sizeof (double[len]));
De acuerdo con el estándar, la expresión sizeof
evalúa el tipo . Qué significa eso? Claramente tiene que evaluar el valor actual de len
. Otro ejemplo:
size_t func(void);
printf("sizeof (double[func()]) = %zu/n", sizeof (double[func()]));
Aquí el nombre del tipo incluye una llamada a la función. La evaluación de la expresión sizeof
debe llamar a la función.
Pero en todos estos casos, no hay necesidad real de evaluar los elementos del objeto de la matriz (si hay alguno), y no tiene sentido hacerlo.
sizeof
aplicado a cualquier otra cosa que no sea un VLA puede evaluarse en tiempo de compilación. La diferencia cuando se aplica sizeof
a un VLA (ya sea un objeto o un tipo) es que algo debe evaluarse en tiempo de ejecución. Pero lo que hay que evaluar no es el operando de sizeof
; es lo que se necesita para determinar el tamaño del operando, que nunca es el operando en sí mismo.
El estándar dice que el operando de sizeof
se evalúa si ese operando es de tipo de matriz de longitud variable. Eso es un defecto en la norma.
Volviendo al ejemplo en la pregunta:
int foo = 100;
double (*bar)[foo] = NULL;
printf("sizeof *bar = %zu/n", sizeof *bar);
He agregado una inicialización a NULL
para que sea aún más claro que la bar
desreferenciación tiene un comportamiento indefinido.
*bar
es de tipo int[foo]
, que es un tipo VLA. En principio, se evalúa *bar
, lo que tendría un comportamiento indefinido ya que la bar
no está inicializada. Pero una vez más, no hay necesidad de desreferenciar la bar
. El compilador generará algo de código cuando procese el tipo int[foo]
, incluido guardar el valor de foo
(o foo * sizeof (int)
) en una variable anónima. Todo lo que tiene que hacer para evaluar sizeof *bar
es recuperar el valor de esa variable anónima. Y si la norma se actualizara para definir la semántica de sizeof
consistente , sería claro que la evaluación de sizeof *bar
está bien definida y produce 100 * sizeof (double)
sin tener que desreferir la bar
referencia.
Sí, esto causa un comportamiento indefinido.
En N1570 6.5.3.4/2 tenemos:
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.
Ahora tenemos la pregunta: ¿es el tipo de *bar
un tipo de matriz de longitud variable?
Dado que la bar
se declara como puntero a VLA, la anulación de la referencia debe producir un VLA. (Pero no veo un texto concreto que especifique si lo hace o no).
Nota: Se podría tener más discusión aquí, quizás se podría argumentar que *bar
tiene el tipo double[100]
que no es un VLA .
Suponiendo que estemos de acuerdo en que el tipo de *bar
es en realidad un tipo de VLA, entonces en la sizeof *bar
se evalúa la expresión *bar
.
bar
es indeterminada en este punto. Ahora mirando a 6.3.2.1/1:
si un lvalue no designa un objeto cuando se evalúa, el comportamiento no está definido
Como la bar
no apunta a un objeto (por ser indeterminado), la *bar
evaluación *bar
provoca un comportamiento indefinido.