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

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

causa una segfault, 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); 

impresiones 5.

Basado en esto, he conjeturado que la notación de inicialización del array, {}, 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 por 5, y las ubicaciones de memoria después de que se almacena el puntero se llenan por 2, 1 y 4. Por lo tanto, nums [0] intenta deshacerse de 5, causando una segfault.

Si estoy equivocado, por favor corrígeme. Y si estoy en lo cierto, por favor amplíe, porque no entiendo por qué los inicializadores de matriz funcionan de la manera en que lo hacen.

Hay una regla (estúpida) en C que dice que cualquier variable plana se puede inicializar con una lista de inicializadores incluidos, como si fuera una matriz.

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

Entonces cuando escribe 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 única, por lo que solo se le asignará el primer valor 5, el rest de la lista se ignorará (en realidad, no creo que el código con inicializadores excedentes deba incluso comstackrse con un comstackdor estricto): no lo hace escribir en la memoria en absoluto. El código es equivalente a int *nums = 5; . Lo que significa que los nums deben apuntar a la dirección 5 .

En este punto, ya debería haber recibido dos advertencias / compiler errors:

  • Asignando un entero al puntero sin un molde.
  • Exceso de elementos en la lista de inicializadores.

Y luego, por supuesto, el código se bloqueará y se quemará, ya que lo más probable es que el 5 no sea una dirección válida, se le permite desreferenciarse con nums[0] .

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


No estoy muy seguro de lo que intenta hacer aquí, pero si desea establecer un puntero para apuntar a una matriz, debe hacer:

 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 excesivos” no es válida C, es una extensión de GCC .

El estándar 6.7.9 Inicialización dice (énfasis mío):

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

/ – /

11 El inicializador para un escalar debe ser una sola expresión, opcionalmente encerrado 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 de escalar para ser la versión no calificada de su tipo declarado.

“Tipo escalar” es un término estándar que hace referencia a variables individuales que no son de tipo array, struct o union (se denominan “tipo agregado”).

Por lo tanto, en un inglés sencillo, el estándar dice: “cuando inicializas una variable, siéntase libre de agregar algunos refuerzos adicionales alrededor de la expresión del inicializador, simplemente porque puede”.

ESCENARIO 1

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

¿Por qué este segfault?

Usted declaró nums como un puntero a int, es decir, se supone que nums contiene la dirección de un entero en la memoria.

Luego intentó inicializar nums en una matriz de valores múltiples . 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ás exactamente el mismo efecto si haces 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 valores múltiples 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ás advertencias por cada valor adicional que no se supone que debe estar en la tarea:

warning: excess elements in scalar initializer .

Para el caso de la asignación de valores múltiples a la variable de puntero, el progtwig falla de forma predeterminada cuando accede a nums[0] , lo que significa que está haciendo referencia literalmente a lo que está almacenado en la dirección 5 . No asignó ninguna memoria válida para nums puntero en este caso.

Vale la pena señalar que no existe una segfault para el caso de asignar valores múltiples a la variable int (aquí no está desreferenciando ningún puntero inválido).


ESCENARIO 2

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

Este no segfault, porque estás asignando legalmente una matriz de 4 ints en la stack.


ESCENARIO 3

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

Este no segfault como se esperaba, porque está imprimiendo el valor del puntero en sí , NO lo que está desreferenciando (que es el acceso a memoria no válido).


Otros

Casi siempre está condenado al segfault cuando codifica el valor de un puntero como este (porque es la tarea del sistema operativo determinar qué proceso puede acceder a qué ubicación de memoria).

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

Por lo tanto, una regla empírica es inicializar siempre un puntero a la dirección de alguna variable asignada , como por ejemplo:

 int a; int *nums = &a; 

o,

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

int *nums = {5, 2, 1, 4}; es 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 parece una extensión útil para mí, pero supongo que la base de desarrolladores lo quiere).

Para evitar este comportamiento (o al menos, recibir una advertencia), puede comstackr 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, la versión int nums[] es generalmente mejor ya que usa menos almacenamiento, y puede usar sizeof para detectar cuánto tiempo es la matriz.

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

nums es un puntero de tipo int . Por lo tanto, debe señalar este punto a alguna ubicación de memoria válida. num[0] está intentando 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á intentando desreferenciarlo, que es un comportamiento indefinido en su sistema. (Parece que 5 no es una ubicación de memoria válida en su sistema)

Mientras

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

es una statement válida donde dice que nums es una matriz de tipo int y la memoria se asigna en función del número de elementos que se pasan durante la inicialización.

Asignando {5, 2, 1, 4}

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

está asignando 5 a nums (después de una conversión de tipo implícito de int a puntero a int). Dereferring realiza una llamada de acceso a la ubicación de memoria en 0x5 . Es posible que no se permita el acceso de su progtwig.

Tratar

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