“int *nums = {5, 2, 1, 4}” provoca un error de segmentación

10 minutos de lectura

avatar de usuario
usuario1299784

int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);

causa una falla de segmento, mientras que

int nums[] = {5, 2, 1, 4};
printf("%d\n", nums[0]);

no Ahora:

int *nums = {5, 2, 1, 4};
printf("%d\n", nums);

estampas 5.

Basado en esto, he conjeturado que la notación de inicialización de matriz, {}, carga ciegamente estos datos en cualquier variable que esté a la izquierda. cuando es int[], la matriz se llena como se desee. Cuando es int*, el puntero se llena con 5, y las ubicaciones de memoria después de donde se almacena el puntero se llenan con 2, 1 y 4. Así que nums[0] intenta desref 5, provocando una falla de segmento.

Si me equivoco, por favor corrígeme. Y si no me equivoco, explíquelo, porque no entiendo por qué los inicializadores de matriz funcionan de la forma en que lo hacen.

  • posible duplicado de Inicializar “un puntero a una matriz de enteros”

    – GSerg

    8 de febrero de 2016 a las 10:17

  • Compile con todas las advertencias habilitadas y su compilador debería decirle qué sucede.

    – Jabberwocky

    8 de febrero de 2016 a las 10:45

  • @GSerg Eso no está cerca de un duplicado. No hay un puntero de matriz en esta pregunta. Aunque algunas respuestas en esa publicación son similares a las de aquí.

    – Lundin

    8 de febrero de 2016 a las 10:46


  • @Lundin Estaba 30% seguro, así que no voté para cerrar, solo publiqué el enlace.

    – GSerg

    08/02/2016 a las 14:55

  • Adquiera el hábito de ejecutar GCC con -pedantic-errors marcar y ver el diagnóstico. int *nums = {5, 2, 1, 4}; no es válido c

    – Ant

    8 de febrero de 2016 a las 23:07


avatar de usuario
Lundin

Hay una regla (estúpida) en C que dice que cualquier variable simple puede inicializarse con una lista de inicializadores entre llaves, como si fuera una matriz.

Por ejemplo puedes escribir int x = {0};que es completamente equivalente a int x = 0;.

Así que cuando escribes int *nums = {5, 2, 1, 4}; en realidad está dando una lista de inicializadores a una sola variable de puntero. Sin embargo, es solo una variable, por lo que solo se le asignará el primer valor 5, el resto de la lista se ignora (en realidad, no creo que el código con exceso de inicializadores deba compilarse con un compilador estricto) – no lo hace ser escrito en la memoria en absoluto. El código es equivalente a int *nums = 5;. Lo que significa, numsdebe apuntar a habla a 5.

En este punto, ya debería haber recibido dos advertencias/errores del compilador:

  • Asignación de enteros a punteros sin conversión.
  • Exceso de elementos en la lista de inicializadores.

Y luego, por supuesto, el código se bloqueará y se quemará desde 5 lo más probable es que no sea una dirección válida a la que se le permita desreferenciar nums[0].

Como nota al margen, debe printf direcciones de puntero con el %p especificador o de lo contrario está invocando un comportamiento indefinido.


No estoy muy seguro de lo que está tratando de hacer aquí, pero si desea configurar un puntero para que apunte a una matriz, debe hacer lo siguiente:

int nums[] = {5, 2, 1, 4};
int* ptr = nums;

// or equivalent:
int* ptr = (int[]){5, 2, 1, 4};

O si desea crear una matriz de punteros:

int* ptr[] = { /* whatever makes sense here */ };

EDITAR

Después de algunas investigaciones, puedo decir que la “lista de inicializadores de elementos en exceso” no es C válida, es una extensión GCC.

El estandar 6.7.9 Inicialización dice (énfasis mío):

2 Ningún inicializador intentará proporcionar un valor para un objeto que no esté contenido dentro de la entidad que se está inicializando.

/–/

11 El inicializador de un escalar será una sola expresión, opcionalmente encerrada entre llaves. El valor inicial del objeto es el de la expresión (después de la conversión); se aplican las mismas restricciones de tipo y conversiones que para la asignación simple, tomando el tipo del escalar como la versión no calificada de su tipo declarado.

“Tipo escalar” es un término estándar que se refiere a variables individuales que no son de tipo matriz, estructura o unión (esas se denominan “tipo agregado”).

Entonces, en lenguaje sencillo, el estándar dice: “cuando inicialice una variable, siéntase libre de agregar algunas llaves adicionales alrededor de la expresión del inicializador, solo porque puede”.

  • No hay nada de “estúpido” en la capacidad de inicializar un objeto escalar con un solo valor encerrado en {}. Por el contrario, facilita uno de los modismos más importantes y convenientes del lenguaje C: { 0 } como inicializador de cero universal. Todo en C se puede inicializar a cero a través de = { 0 }. Esto es muy importante para escribir código independiente del tipo.

    – Ant

    08/02/2016 a las 23:05


  • @AnT No existe tal cosa como un “inicializador cero universal”. En el caso de agregados, {0} simplemente significa inicializar el primer objeto a cero e inicializar el resto de los objetos como si tuvieran una duración de almacenamiento estática. Diría que esto es por coincidencia en lugar de diseño de lenguaje intencional de algún “inicializador universal”, ya que {1} no inicializa todos los objetos a 1.

    – Lundin

    9 de febrero de 2016 a las 7:19

  • @Lundin C11 6.5.16.1/1 cubiertas p = 5; (ninguno de los casos enumerados se cumple para asignar un número entero a un puntero); y 6.7.9/11 dice que las restricciones para la asignación también se utilizan para la inicialización.

    –MM

    9 de febrero de 2016 a las 8:04


  • @Lundin: Sí, lo hay. Es completamente irrelevante qué mecanismo inicializa qué parte del objeto. También es completamente irrelevante si {} la inicialización de escalares está permitida específicamente para ese propósito. Lo único que importa es que = { 0 } se garantiza que el inicializador inicializar a cero todo el objetoque es exactamente lo que lo convirtió en un clásico y uno de los modismos más elegantes del lenguaje C.

    – Ant

    9 de febrero de 2016 a las 18:04

  • @Lundin: Tampoco me queda completamente claro cuál es su comentario sobre {1} tiene que ver con el tema. Nadie nunca afirmó eso {0} interpreta que 0 como un inicializador múltiple para todos y cada uno de los miembros del agregado.

    – Ant

    9 de febrero de 2016 a las 18:04

avatar de usuario
artm

ESCENARIO 1

int *nums = {5, 2, 1, 4};    // <-- assign multiple values to a pointer variable
printf("%d\n", nums[0]);    // segfault

¿Por qué falla este?

declaraste nums como un puntero a int – eso es nums se supone que tiene la dirección de una entero en la memoria.

A continuación, trató de inicializar nums a una matriz de múltiple valores. Entonces, sin profundizar en muchos detalles, esto es conceptualmente incorrecto – no tiene sentido asignar múltiples valores a una variable que se supone que tiene un valor. En este sentido, vería exactamente el mismo efecto si hace esto:

int nums = {5, 2, 1, 4};    // <-- assign multiple values to an int variable
printf("%d\n", nums);    // also print 5

En cualquier caso (asigne múltiples valores a un puntero o una variable int), lo que sucede entonces es que la variable obtendrá el primer valor que es 5, mientras que los valores restantes se ignoran. Este código cumple, pero recibirá advertencias por cada valor adicional que se supone que no debe estar en la asignación:

warning: excess elements in scalar initializer.

Para el caso de asignar múltiples valores a la variable del puntero, el programa falla cuando accede nums[0]lo que significa que está deferenciando lo que esté almacenado en dirección 5 literalmente. No asignó ninguna memoria válida para el puntero nums en este caso.

Vale la pena señalar que no hay un error de segmento para el caso de asignar múltiples valores a la variable int (no está eliminando la referencia de ningún puntero no válido aquí).


ESCENARIO 2

int nums[] = {5, 2, 1, 4};

Este no falla en el segmento, porque está asignando legalmente una matriz de 4 entradas en la pila.


ESCENARIO 3

int *nums = {5, 2, 1, 4};
printf("%d\n", nums);   // print 5

Este no falla como se esperaba, porque está imprimiendo el valor del propio puntero – NO lo que está desreferenciando (que es acceso a memoria no válido).


Otros

Casi siempre está condenado a fallas de segmento cada vez que codificar el valor de un puntero así (porque es tarea del sistema operativo determinar qué proceso puede acceder a qué ubicación de memoria).

int *nums = 5;    // <-- segfault

Entonces, una regla general es siempre inicializar un puntero a la dirección de algún asignado variables, tales como:

int a;
int *nums = &a;

o,

int a[] = {5, 2, 1, 4};
int *nums = a; 

  • +1 Ese es un buen consejo, pero “nunca” realmente es demasiado fuerte, dadas las direcciones mágicas en muchas plataformas. (Usar tablas constantes para esas direcciones fijas no apunta a variables existentes y, por lo tanto, viola su regla como se indica). Las cosas de bajo nivel, como el desarrollo de controladores, se ocupan de ese tipo de cosas con bastante frecuencia.

    – El Nate

    8 de febrero de 2016 a las 15:42


  • “Esto es válido”: ignorar el exceso de inicializadores es una extensión GCC; en el estándar C que no está permitido

    –MM

    08/02/2016 a las 20:39

  • @TheNate: sí, tienes razón. Edité en base a tu comentario, gracias.

    – artm

    8 de febrero de 2016 a las 23:02

  • @MM: gracias por señalarlo. Edité para eliminar eso.

    – artm

    08/02/2016 a las 23:15

int *nums = {5, 2, 1, 4}; es un código mal formado. Hay una extensión GCC que trata este código de la misma manera que:

int *nums = (int *)5;

intentando formar un puntero a la dirección de memoria 5. (Esto no me parece una extensión útil, pero supongo que la base de desarrolladores la quiere).

Para evitar este comportamiento (o al menos, obtener una advertencia), puede compilar en modo estándar, por ejemplo -std=c11 -pedantic.

Una forma alternativa de código válido sería:

int *nums = (int[]){5, 2, 1, 4};

que apunta a un literal mutable de la misma duración de almacenamiento que nums. Sin embargo, el int nums[] generalmente es mejor, ya que usa menos almacenamiento, y puede usar sizeof para detectar la longitud de la matriz.

  • ¿Se garantizaría que la matriz en la forma de literal compuesto tenga una vida útil de almacenamiento al menos tan larga como la de nums?

    – Super gato

    08/02/2016 a las 19:20

  • @supercat sí, es automático si nums es automático y estático si nums es estático

    –MM

    8 febrero 2016 a las 20:18

  • @MM: ¿Se aplicaría eso incluso si nums ¿Se declara una variable estática dentro de una función, o el compilador tendría derecho a limitar la vida útil de la matriz a la del bloque que la encierra, incluso si se estuviera asignando a una variable estática?

    – Super gato

    08/02/2016 a las 20:49

  • @supercat sí (el primer bit). La segunda opción significaría UB la segunda vez que se llama a la función (ya que las variables estáticas solo se inicializan en la primera llamada)

    –MM

    08/02/2016 a las 20:50

avatar de usuario
Gopi

int *nums = {5, 2, 1, 4};

nums es un puntero de tipo int. Por lo tanto, debe señalar este punto en alguna ubicación de memoria válida. num[0] está tratando de desreferenciar alguna ubicación de memoria aleatoria y, por lo tanto, la falla de segmentación.

Sí, el puntero tiene el valor 5 y está tratando de quitarle la referencia, lo que es un comportamiento indefinido en su sistema. (Parece 5 no es una ubicación de memoria válida en su sistema)

Mientras que

int nums[] = {1,2,3,4};

es una declaración válida donde dices nums es una matriz de tipo int y la memoria se asigna en función del número de elementos pasados ​​durante la inicialización.

avatar de usuario
fahad siddiqui

al asignar {5, 2, 1, 4}

int *nums = {5, 2, 1, 4};

le estás asignando 5 a nums (después de un encasillado implícito de int a puntero a int). Al desreferirlo, se realiza una llamada de acceso a la ubicación de la memoria en 0x5. Es posible que su programa no pueda acceder a eso.

Tratar

printf("%p", (void *)nums);

¿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