¿Qué sucede con una variable no inicializada declarada en C? ¿Tiene un valor?

10 minutos de lectura

avatar de usuario
atp

Si en CI escribe:

int num;

Antes de asignar nada a numes el valor de num ¿indeterminado?

  • Um, ¿no es eso un definido variable, no declarado ¿uno? (Lo siento si ese es mi C++ brillando…)

    – sbi

    20 de octubre de 2009 a las 21:32

  • No. Puedo declarar una variable sin definirla: extern int x; Sin embargo, definir siempre implica declarar. Esto no es cierto en C++, con las variables miembro de clase estáticas se pueden definir sin declarar, ya que la declaración debe estar en la definición de clase (¡no en la declaración!) y la definición debe estar fuera de la definición de clase.

    – bdonlan

    20 de octubre de 2009 a las 21:36

  • ee.hawaii.edu/~tep/EE160/Book/chap14/subsection2.1.1.4.html Parece definido significa que también debe inicializarlo.

    – atp

    20 de octubre de 2009 a las 21:38

avatar de usuario
Ciro Santilli Путлер Капут 六四事

Ejemplo de Ubuntu 15.10, Kernel 4.2.0, x86-64, GCC 5.2.1

Suficientes estándares, veamos una implementación 🙂

Variable local

Estándares: comportamiento indefinido.

Implementación: el programa asigna espacio en la pila y nunca mueve nada a esa dirección, por lo que se usa lo que estaba allí anteriormente.

#include <stdio.h>
int main() {
    int i;
    printf("%d\n", i);
}

compilar con:

gcc -O0 -std=c99 a.c

salidas:

0

y descompila con:

objdump -dr a.out

para:

0000000000400536 <main>:
  400536:       55                      push   %rbp
  400537:       48 89 e5                mov    %rsp,%rbp
  40053a:       48 83 ec 10             sub    $0x10,%rsp
  40053e:       8b 45 fc                mov    -0x4(%rbp),%eax
  400541:       89 c6                   mov    %eax,%esi
  400543:       bf e4 05 40 00          mov    $0x4005e4,%edi
  400548:       b8 00 00 00 00          mov    $0x0,%eax
  40054d:       e8 be fe ff ff          callq  400410 <printf@plt>
  400552:       b8 00 00 00 00          mov    $0x0,%eax
  400557:       c9                      leaveq
  400558:       c3                      retq

Según nuestro conocimiento de las convenciones de llamadas x86-64:

  • %rdi es el primer argumento de printf, por lo tanto, la cadena "%d\n" en la dirección 0x4005e4

  • %rsi es el segundo argumento de printf, por lo tanto i.

    Viene de -0x4(%rbp)que es la primera variable local de 4 bytes.

    En este punto, rbp está en la primera página de la pila que ha sido asignado por el núcleo, por lo que para comprender ese valor, deberíamos mirar el código del núcleo y averiguar en qué lo establece.

    TODO, ¿establece el kernel esa memoria en algo antes de reutilizarla para otros procesos cuando un proceso muere? De lo contrario, el nuevo proceso podría leer la memoria de otros programas terminados, filtrando datos. Consulte: ¿Los valores no inicializados representan alguna vez un riesgo para la seguridad?

Luego, también podemos jugar con nuestras propias modificaciones de pila y escribir cosas divertidas como:

#include <assert.h>

int f() {
    int i = 13;
    return i;
}

int g() {
    int i;
    return i;
}

int main() {
    f();
    assert(g() == 13);
}

Tenga en cuenta que GCC 11 parece producir una salida de ensamblaje diferente, y el código anterior deja de “funcionar”, después de todo es un comportamiento indefinido: ¿Por qué -O3 en gcc parece inicializar mi variable local en 0, mientras que -O0 no?

Variable local en -O3

Análisis de implementación en: ¿Qué significa en gdb?

Variables globales

Estándares: 0

Implementación: .bss sección.

#include <stdio.h>
int i;
int main() {
    printf("%d\n", i);
}

gcc -00 -std=c99 a.c

compila a:

0000000000400536 <main>:
  400536:       55                      push   %rbp
  400537:       48 89 e5                mov    %rsp,%rbp
  40053a:       8b 05 04 0b 20 00       mov    0x200b04(%rip),%eax        # 601044 <i>
  400540:       89 c6                   mov    %eax,%esi
  400542:       bf e4 05 40 00          mov    $0x4005e4,%edi
  400547:       b8 00 00 00 00          mov    $0x0,%eax
  40054c:       e8 bf fe ff ff          callq  400410 <printf@plt>
  400551:       b8 00 00 00 00          mov    $0x0,%eax
  400556:       5d                      pop    %rbp
  400557:       c3                      retq
  400558:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  40055f:       00

# 601044 <i> dice que i está en la dirección 0x601044 y:

readelf -SW a.out

contiene:

[25] .bss              NOBITS          0000000000601040 001040 000008 00  WA  0   0  4

que dice 0x601044 está justo en el medio de la .bss sección, que comienza en 0x601040 y tiene una longitud de 8 bytes.

Él estándar ELF luego garantiza que la sección nombrada .bss está completamente lleno de ceros:

.bss Esta sección contiene datos no inicializados que contribuyen a la imagen de memoria del programa. Por definición, el sistema inicializa los datos con ceros cuando el programa comienza a ejecutarse. La sección no ocupa espacio de archivo, como lo indica el tipo de sección, SHT_NOBITS.

Además, el tipo SHT_NOBITS es eficiente y no ocupa espacio en el archivo ejecutable:

sh_size Este miembro proporciona el tamaño de la sección en bytes. A menos que el tipo de sección sea SHT_NOBITS la sección ocupa sh_size
bytes en el archivo. Una sección de tipo SHT_NOBITS puede tener un tamaño distinto de cero, pero no ocupa espacio en el archivo.

Luego, depende del kernel de Linux poner a cero esa región de memoria al cargar el programa en la memoria cuando se inicia.

  • No puedo entender (bueno yo muy bien puede) por qué esto tiene muchos menos votos a favor que el de DigitalRoss solo un minuto después 😀

    – Antti Haapala — Слава Україні

    8 de agosto de 2017 a las 23:04

avatar de usuario
DigitalRoss

0 si estático o global, indeterminado si la clase de almacenamiento es automática

C siempre ha sido muy específico sobre los valores iniciales de los objetos. Si es mundial o static, se pondrán a cero. Si autoel valor es indeterminado.

Este fue el caso en los compiladores anteriores a C89 y así lo especificaba K&R y en el informe C original de DMR.

Este fue el caso en C89, ver sección 6.5.7 Inicialización.

Si un objeto que tiene duración de almacenamiento automático no se inicializa explícitamente, su valor es indeterminado. Si un objeto que tiene una duración de almacenamiento estático no se inicializa explícitamente, se inicializa implícitamente como si a cada miembro que tiene un tipo aritmético se le asignara 0 y a cada miembro que tiene un tipo de puntero se le asignara una constante de puntero nulo.

Este fue el caso en C99, consulte la sección 6.7.8 Inicialización.

Si un objeto que tiene duración de almacenamiento automático no se inicializa explícitamente, su valor es indeterminado. Si un objeto que tiene una duración de almacenamiento estático no se inicializa explícitamente, entonces:
— si tiene un tipo de puntero, se inicializa a un puntero nulo;
— si es de tipo aritmético, se inicializa a cero (positivo o sin signo);
— si es un agregado, cada miembro se inicializa (recursivamente) de acuerdo con estas reglas;
— si es una unión, el primer miembro nombrado se inicializa (recursivamente) de acuerdo con estas reglas.

En cuanto a qué exactamente indeterminado significa, no estoy seguro para C89, C99 dice:

3.17.2
valor indeterminado

ya sea un valor no especificado o una representación de trampa

Pero independientemente de lo que digan los estándares, en la vida real, cada página de pila en realidad comienza como cero, pero cuando su programa mira cualquier auto valores de clase de almacenamiento, ve todo lo que dejó su propio programa la última vez que usó esas direcciones de pila. Si asigna mucho auto arreglos, los verá eventualmente comenzar ordenadamente con ceros.

Te preguntarás, ¿por qué es así? Una respuesta SO diferente se ocupa de esa pregunta, consulte: https://stackoverflow.com/a/2091505/140740

  • indeterminado generalmente (¿usado?) significa que puede hacer cualquier cosa. Puede ser cero, puede ser el valor que estaba allí, puede bloquear el programa, puede hacer que la computadora produzca panqueques de arándanos de la ranura del CD. usted no tiene absolutamente ninguna garantía. Podría causar la destrucción del planeta. Al menos en lo que respecta a las especificaciones… cualquiera que haya creado un compilador que realmente hiciera algo así estaría muy mal visto B-)

    –Brian Postow

    20 de octubre de 2009 a las 22:02

  • En el borrador C11 N1570, definición de indeterminate value se puede encontrar en 3.19.2.

    – usuario3528438

    26/02/2016 a las 22:40

  • ¿Es para que siempre dependa del compilador o del sistema operativo qué valor establece para la variable estática? Por ejemplo, si alguien escribe un sistema operativo o un compilador propio, y si también establece el valor inicial de forma predeterminada para la estática como indeterminado, ¿es eso posible?

    – Aditya Singh

    30 de enero de 2017 a las 17:41

  • @AdityaSingh, el sistema operativo puede hacerlo más fácil en el compilador pero, en última instancia, es la responsabilidad principal del compilador ejecutar el catálogo de código C existente en el mundo y una responsabilidad secundaria cumplir con los estándares. sin duda sería posible hacerlo de otra manera, pero, ¿por qué? Además, es complicado hacer que los datos estáticos sean indeterminados, porque el sistema operativo De Verdad desea poner a cero las páginas primero por razones de seguridad. (Las variables automáticas son solo superficialmente impredecibles porque su propio programa generalmente ha estado usando esas direcciones de pila en un punto anterior).

    – DigitalRoss

    30 de enero de 2017 a las 19:23


  • @BrianPostow No, eso no es correcto. Consulte stackoverflow.com/a/40674888/584518. Usar un valor indeterminado causa no especificado comportamiento, no un comportamiento indefinido, salvo en el caso de las representaciones trampa.

    – Lundin

    12 de septiembre de 2019 a las 6:56


avatar de usuario
Rajeev Kumar

Por lo que he ido, depende principalmente del compilador, pero en general, en la mayoría de los casos, los compiladores asumen el valor como 0.
Obtuve un valor de basura en el caso de VC ++, mientras que TC dio un valor de 0. Lo imprimo como se muestra a continuación

int i;
printf('%d',i);

  • indeterminado generalmente (¿usado?) significa que puede hacer cualquier cosa. Puede ser cero, puede ser el valor que estaba allí, puede bloquear el programa, puede hacer que la computadora produzca panqueques de arándanos de la ranura del CD. usted no tiene absolutamente ninguna garantía. Podría causar la destrucción del planeta. Al menos en lo que respecta a las especificaciones… cualquiera que haya creado un compilador que realmente hiciera algo así estaría muy mal visto B-)

    –Brian Postow

    20 de octubre de 2009 a las 22:02

  • En el borrador C11 N1570, definición de indeterminate value se puede encontrar en 3.19.2.

    – usuario3528438

    26/02/2016 a las 22:40

  • ¿Es que siempre depende del compilador o del sistema operativo qué valor establece para la variable estática? Por ejemplo, si alguien escribe un sistema operativo o un compilador propio, y si también establece el valor inicial de forma predeterminada para la estática como indeterminado, ¿es eso posible?

    – Aditya Singh

    30 de enero de 2017 a las 17:41

  • @AdityaSingh, el sistema operativo puede hacerlo más fácil en el compilador pero, en última instancia, es la responsabilidad principal del compilador ejecutar el catálogo de código C existente en el mundo y una responsabilidad secundaria cumplir con los estándares. sin duda sería posible hacerlo de otra manera, pero, ¿por qué? Además, es complicado hacer que los datos estáticos sean indeterminados, porque el sistema operativo De Verdad desea poner a cero las páginas primero por razones de seguridad. (Las variables automáticas son solo superficialmente impredecibles porque su propio programa generalmente ha estado usando esas direcciones de pila en un punto anterior).

    – DigitalRoss

    30 de enero de 2017 a las 19:23


  • @BrianPostow No, eso no es correcto. Consulte stackoverflow.com/a/40674888/584518. Usar un valor indeterminado causa no especificado comportamiento, no un comportamiento indefinido, salvo en el caso de las representaciones trampa.

    – Lundin

    12 de septiembre de 2019 a las 6:56


avatar de usuario
Simón

La respuesta básica es, sí, no está definido.

Si observa un comportamiento extraño debido a esto, puede depender de dónde se declare. Si está dentro de una función en la pila, lo más probable es que el contenido sea diferente cada vez que se llame a la función. Si es un ámbito estático o de módulo, no está definido pero no cambiará.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad