¿Por qué no inicializado en lugar de fuera de los límites?

7 minutos de lectura

avatar de usuario
Goswin de Brederlow

En el siguiente código, ¿por qué es b[9] no inicializado en lugar de fuera de los límites?

#include <stdio.h>

int main(void)
{
    char b[] = {'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!'};
    printf("b[9] = %d\n", b[9]);

    return 0;
}

Llamada del compilador:

% gcc -O2 -W -Wall -pedantic -c foo.c
foo.c: In function ‘main’:
foo.c:6:5: warning: ‘b[9]’ is used uninitialized in this function [-Wuninitialized]
     printf("b[9] = %d\n", b[9]);
% gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.6) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Actualización: ahora esto es extraño:

#include <stdio.h>

void foo(char *);

int main(void)
{
    char b[] = {'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!'};
    foo(&b[9]);
    foo(&b[10]);
    printf("b[9] = %d\n", b[9]);
    printf("b[10] = %d\n", b[10]);

    return 0;
}

Compilar esto da como resultado las advertencias que uno esperaría:

% gcc -O2 -W -Wall -pedantic -c foo.c
foo.c: In function ‘main’:
foo.c:9:5: warning: array subscript is above array bounds [-Warray-bounds]
     foo(&b[10]);
     ^
foo.c:10:29: warning: array subscript is above array bounds [-Warray-bounds]
     printf("b[9] = %d\n", b[9]);
                             ^
foo.c:11:29: warning: array subscript is above array bounds [-Warray-bounds]
     printf("b[10] = %d\n", b[10]);

De repente, gcc ve el fuera de los límites por lo que es.

  • Curiosamente suenan obtiene bien.

    – Gaurav Sehgal

    17 de julio de 2018 a las 12:45

  • Probar printf("b[10] = %d\n", b[10]); 9 es uno más allá del final de la matriz, y es una dirección permitida (aunque todavía no está definido para quitarle la referencia…).

    –Andrew Henle

    17/07/2018 a las 12:50

  • @AndrewHenle Pero b[9] es una desreferencia, &b[9] sería válido. Algunas rarezas más añadidas a la pregunta.

    – Goswin de Brederlow

    17 de julio de 2018 a las 13:02

  • Uno más allá del final de la matriz puede tratarse de manera diferente, y en su primer caso, no del todo correctamente. Consulte los párrafos sobre aritmética de punteros en el estándar C: https://port70.net/~nsz/c/c11/n1570.html#6.5.6p8

    –Andrew Henle

    17 de julio de 2018 a las 13:08


  • Las diferentes advertencias son probablemente de diferentes versiones de gcc. los comportamientos de ambas cosas sus muestras no están definidas por los estándares, por lo que los compiladores no son realmente requerido para hacer algo en particular con ellos – no se requieren advertencias. El problema para un compilador-desarrollador es que el comportamiento indefinido puede manifestarse en un número ilimitado de formas. Por lo tanto, es difícil para un compilador determinar rápidamente (en el sentido de que los programadores no se quejan de que lleva demasiado tiempo compilar) determinar qué advertencia es la “mejor”.

    – Pedro

    17 de julio de 2018 a las 13:34

avatar de usuario
Antti Haapala — Слава Україні

Creo que este podría ser el caso aquí: en el primer código, GCC nota que no necesita toda la matriz de caracteres, solo b[9]por lo que puede reemplazar el código con

char b_9; // = ???
printf("b[9] = %d\n", b_9);

Ahora, este es un completamente transformación legal, porque como se accedió a la matriz fuera de los límites, el comportamiento es completamente indefinido. Solo en la última fase se da cuenta de que esta variable, que es un sustituto de b[9]no está inicializado y emite el mensaje de diagnóstico.

¿Por qué creo esto? porque si agrego sólo cualquier código que referencia la dirección de la matriz en memoriapor ejemplo printf("%p\n", &b[8]); en cualquier lugar, la matriz ahora está completamente realizada en la memoria, y el compilador diagnosticará el subíndice de la matriz está por encima de los límites de la matriz.


Lo que encuentro aún más interesante es que GCC no diagnostica el acceso fuera de los límites a menos que las optimizaciones estén habilitadas. Esto nuevamente sugeriría que cada vez que esté escribiendo un programa nuevo, debe compilarlo con optimizaciones habilitadas para hacer que los errores sean muy visibles en lugar de mantenerlos ocultos con el modo de depuración;)

  • Estoy de acuerdo. El código gcc -O0 es lento, ilegible, omite muchas advertencias y tiene poca relevancia para cualquier cosa que desee enviar. Siempre debe usar -Os o -O2.

    – Goswin de Brederlow

    6 de agosto de 2018 a las 16:14

avatar de usuario
Betsabé

El comportamiento en la lectura b[9] o b[10] es indefinido.

Su compilador está emitiendo una advertencia (no es necesario), aunque el texto de advertencia es un poco engañoso, pero técnicamente no es incorrecto. En mi opinión, es bastante inteligente. (El compilador AC es no necesario para emitir un diagnóstico de acceso fuera de los límites).

Con respecto a &b[9]el compilador es no permitido desreferenciar eso, y debe evaluarlo como b + 9. Se le permite establecer un puntero uno más allá del final de una matriz. El comportamiento de establecer un puntero en &b[10] es indefinido.

  • Obviamente. La pregunta es específicamente sobre la advertencia que muestra gcc. No sobre el comportamiento o la validez del código. Gcc ahora lo hace con los errores fuera de los límites y la pregunta es por qué no lo usa aquí. Vea la actualización para más extrañeza.

    – Goswin de Brederlow

    17 de julio de 2018 a las 13:05


  • @GoswinvonBrederlow: La advertencia de gcc es correcta. b[9] no está inicializado.

    – Betsabé

    17 de julio de 2018 a las 13:06

  • Realmente no está sin inicializar. Está fuera de los límites. desreferenciarlo es simplemente incorrecto.

    – Goswin de Brederlow

    17 de julio de 2018 a las 13:08

  • @GoswinvonBrederlow: Podemos estar de acuerdo en no estar de acuerdo en ese punto. Tu pregunta ahora es más interesante ahora que agregaste &b[9] &C.

    – Betsabé

    17 de julio de 2018 a las 13:09

  • Verdadero b[9] no está inicializado. también está fuera de la b[] formación. Traté de “inicializar” b[9] y todavía tiene sólo 9 elementos. El resultado admite “el texto de advertencia es un poco engañoso, pero técnicamente no es incorrecto”.

    – chux – Reincorporar a Monica

    17 de julio de 2018 a las 14:42


Algunos resultados experimentales adicionales.


Usando char b[9] en vez de char b[] parece no hacer ninguna diferencia, gcc todavía advierte lo mismo con char b[9].

Curiosamente, inicializar el elemento de un paso a través del miembro “siguiente” en un struct 1) silencia la advertencia de “no inicializado” y 2) no advierte sobre la adhesión fuera de la matriz.

#include <stdio.h>

typedef struct {
  char c[9];
  char d[9];
} TwoNines;

int main(void) {
  char b[9] = { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' };
  printf("b[] size %zu\n", sizeof b);
  printf("b[9] = %d\n", b[9]);   // 'b[9]' is used uninitialized in this function [-Wuninitialized]

  TwoNines e = { { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' }, //
                 { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' } };

  printf("e size %zu\n", sizeof e);
  printf("e.c[9] = %d\n", e.c[9]);   // No warning.

  return 0;
}

Producción

b[] size 9
b[9] = 0
e size 18    // With 18, we know `e` is packed.
e.c[9] = 78  // 'N'

Notas:
gcc -std=c11 -O3 -g3 -pedantic -Wall -Wextra -Wconversion -c -fmessage-length=0 -v -MMD -MP …
gcc/gcc-7.3.0-2.i686

  • Volver a acceder al miembro “siguiente”: Eso es legal porque: 1. Un puntero al primer miembro es equivalente a un puntero a toda la estructura, 2. Cualquier objeto se puede convertir de forma segura en una matriz de caracteres y desreferenciarse para inspeccionar la representación de bytes, 3. e.c casualmente resulta ser una matriz de caracteres, por lo que 4. Está escribiendo un juego de palabras de toda la estructura en una matriz de caracteres.

    –Kevin

    18 de julio de 2018 a las 4:46

avatar de usuario
0___________

Cuando compila el código con -O2, la trivialidad del ejemplo hace que esta variable se optimice. Así que la advertencia es 100% correcta.

¿Ha sido útil esta solución?