int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);
causa un defecto de segmento, mientras que
int nums[] = {5, 2, 1, 4};
printf("%d\n", nums[0]);
no lo hace. 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 de matriz, {}, carga ciegamente estos datos en cualquier variable que esté a la izquierda. Cuando es int [], la matriz se completa 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 completan con 2, 1 y 4. Entonces nums [0] intenta eliminar 5, causando un error de segmentación.
Si me equivoco, corrígeme. Y si estoy en lo correcto, por favor explique, porque no entiendo por qué los inicializadores de matriz funcionan de la manera en que lo hacen.
-pedantic-errors
bandera y observe el diagnóstico.int *nums = {5, 2, 1, 4};
no es válido C.Respuestas:
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, puede escribir
int x = {0};
, que es completamente equivalente aint 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, 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 un exceso de inicializadores deba compilarse con un compilador estricto); no es así escribir en la memoria en absoluto. El código es equivalente aint *nums = 5;
. Lo que significa quenums
debe apuntar a la dirección5
.En este punto, ya debería haber recibido dos advertencias / errores del compilador:
Y luego, por supuesto, el código se bloqueará y se quemará, ya que
5
lo más probable es que no sea una dirección válida con la que pueda eliminar la referencianums[0]
.Como nota al margen, debe señalar las
printf
direcciones 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 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 investigar un poco, puedo decir que la "lista de inicializadores de elementos en exceso" no es C válida, es una extensión GCC .
La inicialización estándar 6.7.9 dice (el énfasis es mío):
"Tipo escalar" es un término estándar que se refiere a variables individuales que no son de tipo matriz, estructura o unión (se denominan "tipo agregado").
Entonces, en inglés simple, el estándar dice: "cuando inicializa una variable, no dude en agregar algunas llaves adicionales alrededor de la expresión del inicializador, solo porque puede".
fuente
{}
. Por el contrario, facilita uno de los modismos más importantes y convenientes del lenguaje C:{ 0 }
como inicializador cero universal. Todo en C se puede inicializar a cero mediante= { 0 }
. Esto es muy importante para escribir código independiente del tipo.{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ático. Yo diría que esto es una coincidencia en lugar de un diseño de lenguaje intencional de algún "inicializador universal", ya{1}
que no inicializa todos los objetos a 1.p = 5;
(ninguno de los casos enumerados se cumple para asignar un entero al puntero); y 6.7.9 / 11 dice que las restricciones para la asignación también se utilizan para la inicialización.{}
se permite la inicialización de escalares con ese propósito específicamente. Lo único que importa es que= { 0 }
se garantiza que el inicializador inicializará a cero todo el objeto , que es exactamente lo que lo convirtió en un clásico y uno de los modismos más elegantes del lenguaje C.{1}
tiene que ver su comentario sobre el tema. Nadie afirmó que lo{0}
interpreta0
como un multiinicializador para todos y cada uno de los miembros del agregado.ESCENARIO 1
¿Por qué este segrega?
Declaraste
nums
como un puntero a int, que senums
supone que contiene la dirección de un entero en la memoria.Luego intentó inicializar
nums
a una matriz de múltiples 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 varios 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 de puntero, el programa segrega cuando accede
nums[0]
, lo que significa que está eliminando lo que esté almacenado en la dirección 5 literalmente. Ennums
este caso, no asignó ninguna memoria válida para el puntero .Vale la pena señalar que no hay un error de segmentación para el caso de asignar varios valores a la variable int (no está eliminando la referencia a ningún puntero no válido aquí).
ESCENARIO 2
int nums[] = {5, 2, 1, 4};
Este no segmenta, 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 segmenta como se esperaba, porque está imprimiendo el valor del puntero en sí , NO lo que está desreferenciando (que es un acceso inválido a la memoria).
Otros
Casi siempre está condenado a la falla de segmentación cada vez que
codifica el valor de un punterocomo este (porque es la tarea del sistema operativo determinar qué proceso puede acceder a qué ubicación de memoria).int *nums = 5; // <-- segfault
Entonces, una regla general es inicializar siempre un puntero a la dirección de alguna variable asignada , como:
int a; int *nums = &a;
o,
int a[] = {5, 2, 1, 4}; int *nums = a;
fuente
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, recibir 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, laint nums[]
versión es generalmente mejor ya que usa menos almacenamiento y puede usarlasizeof
para detectar cuánto tiempo dura la matriz.fuente
nums
?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 circundante incluso si se estuviera asignando a una variable estática?int *nums = {5, 2, 1, 4};
nums
es un puntero de tipoint
. Por lo tanto, debe señalar este punto en una ubicación de memoria válida.num[0]
está intentando eliminar la referencia de 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 eliminar la referencia, lo 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 declaración válida en la que está diciendo que
nums
es una matriz de tipoint
y la memoria se asigna en función del número de elementos pasados durante la inicialización.fuente
int *nums = (int[]){5, 2, 1, 4};
Asignando
{5, 2, 1, 4}
int *nums = {5, 2, 1, 4};
está asignando 5 a
nums
(después de un encasillado implícito de int a puntero a int). Deserferirlo hace una llamada de acceso a la ubicación de memoria en0x5
. Es posible que su programa no pueda acceder a eso.Tratar
printf("%p", (void *)nums);
fuente