que - punteros void lenguaje c
¿Es posible inicializar un puntero C a NULL? (9)
Había estado escribiendo cosas como
char *x=NULL;
asumiendo que
char *x=2;
crearía un puntero de caracteres para abordar 2.
Pero, en
The GNU C Programming Tutorial
dice que
int *my_int_ptr = 2;
almacena el valor entero
2
en cualquier dirección aleatoria que se encuentre en
my_int_ptr
cuando se asigna.
Esto parecería implicar que mi propio
char *x=NULL
está asignando cualquier valor de
NULL
a un
char
a alguna dirección aleatoria en la memoria.
Mientras
#include <stdlib.h>
#include <stdio.h>
int main()
{
char *x=NULL;
if (x==NULL)
printf("is NULL/n");
return EXIT_SUCCESS;
}
de hecho, imprime
es nulo
cuando lo compilo y lo ejecuto, me preocupa que confíe en un comportamiento indefinido, o al menos un comportamiento poco especificado, y que deba escribir
char *x;
x=NULL;
en lugar.
¿Es posible inicializar un puntero C a NULL?
TL; DR Sí, mucho.
El reclamo real hecho en la guía dice:
Por otro lado, si usa solo la asignación inicial única,
int *my_int_ptr = 2;
, el programa intentará llenar el contenido de la ubicación de memoria señalada pormy_int_ptr
con el valor 2. Dado quemy_int_ptr
está lleno de basura, puede ser cualquier dirección. [...]
Bueno, están equivocados, tienes razón.
Para la declaración, ( ignorando, por ahora, el hecho de que la conversión de puntero a entero es un comportamiento definido por la implementación )
int * my_int_ptr = 2;
my_int_ptr
es una variable (de tipo puntero a
int
), tiene una dirección propia (tipo: dirección de puntero a entero), está almacenando un valor de
2
en
esa
dirección.
Ahora,
my_int_ptr
, al ser un tipo de puntero, podemos decir que
apunta al
valor de "tipo" en la ubicación de memoria
señalada por
el valor contenido en
my_int_ptr
.
Entonces, esencialmente está asignando el valor
de
la variable del puntero, no el valor de la ubicación de memoria señalada por el puntero.
Entonces, para concluir
char *x=NULL;
inicializa la variable de puntero
x
a
NULL
, no el
valor en la dirección de memoria señalada por el puntero
.
Esto es lo mismo que
char *x;
x = NULL;
Expansión:
Ahora, siendo estrictamente conforme, una declaración como
int * my_int_ptr = 2;
es ilegal, ya que implica violación de restricciones. Para ser claro,
-
my_int_ptr
es una variable de puntero, escribaint *
-
una constante entera,
2
tiene el tipoint
, por definición.
y no son tipos "compatibles", por lo que esta inicialización no es válida porque viola las reglas de asignación simple, mencionadas en el capítulo §6.5.16.1 / P1, descritas en la respuesta de Lundin .
En caso de que alguien esté interesado en cómo se vincula la inicialización con las restricciones de asignación simple, cite
C11
, capítulo §6.7.9, P11
El inicializador para un escalar debe 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.
El tutorial está mal.
En ISO C,
int *my_int_ptr = 2;
es un error
En GNU C, significa lo mismo que
int *my_int_ptr = (int *)2;
.
Esto convierte el entero
2
en una dirección de memoria, de alguna manera según lo determine el compilador.
No intenta almacenar nada en la ubicación a la que se dirige esa dirección (si corresponde).
Si pasó a escribir
*my_int_ptr = 5;
, intentaría almacenar el número
5
en la ubicación a la que se dirige esa dirección.
Esto es correcto.
int main()
{
char * x = NULL;
if (x==NULL)
printf("is NULL/n");
return EXIT_SUCCESS;
}
Esta función es correcta para lo que hace. Asigna la dirección de 0 al puntero de caracteres x. Es decir, apunta el puntero x a la dirección de memoria 0.
Alternativa:
int main()
{
char* x = 0;
if ( !x )
printf(" x points to NULL/n");
return EXIT_SUCCESS;
}
Mi suposición sobre lo que querías es:
int main()
{
char* x = NULL;
x = alloc( sizeof( char ));
*x = ''2'';
if ( *x == ''2'' )
printf(" x points to an address/location that contains a ''2'' /n");
return EXIT_SUCCESS;
}
x is the street address of a house. *x examines the contents of that house.
Me gustaría agregar algo ortogonal a las excelentes respuestas.
En realidad, inicializar a
NULL
está lejos de ser una mala práctica y puede ser útil si ese puntero se puede usar o no para almacenar un bloque de memoria asignado dinámicamente.
int * p = NULL;
...
if (...) {
p = (int*) malloc(...);
...
}
...
free(p);
Dado que según el
estándar ISO-IEC 9899,
free
es un nop cuando el argumento es
NULL
, el código anterior (o algo más significativo en la misma línea) es legítimo.
Mucha confusión sobre los punteros en C proviene de una muy mala elección que se hizo originalmente con respecto al estilo de codificación, corroborada por una muy mala elección en la sintaxis del lenguaje.
int *x = NULL;
es correcto C, pero es muy engañoso, incluso diría absurdo, y ha dificultado la comprensión del lenguaje para muchos novatos.
Hace pensar que más adelante podríamos hacer
*x = NULL;
lo cual por supuesto es imposible.
Verá, el tipo de la variable no es
int
, y el nombre de la variable no es
*x
, ni el
*
en la declaración desempeña ningún papel funcional en colaboración con el
=
.
Es puramente declarativo.
Entonces, lo que tiene mucho más sentido es esto:
int* x = NULL;
que también es correcta C, aunque no se adhiere al estilo de codificación original de K&R.
Deja perfectamente claro que el tipo es
int*
, y la variable de puntero es
x
, por lo que se hace evidente incluso para los no iniciados que el valor
NULL
se almacena en
x
, que es un puntero a
int
.
Además, facilita la derivación de una regla: cuando la estrella está lejos del nombre de la variable, entonces es una declaración, mientras que la estrella que se adjunta al nombre es una referencia de puntero.
Entonces, ahora se vuelve mucho más comprensible que más abajo podamos hacer
x = NULL;
o
*x = 2;
en otras palabras, hace que sea más fácil para un novato ver cómo
variable = expression
conduce a
pointer-type variable = pointer-expression
y
dereferenced-pointer-variable = expression
.
(Para los iniciados, por ''expresión'' me refiero a ''rvalue''.)
La elección desafortunada en la sintaxis del lenguaje es que al declarar variables locales se puede decir
int i, *p;
que declara un entero y un puntero a un entero, por lo que uno cree que el
*
es una parte útil del nombre.
Pero no lo es, y esta sintaxis es solo un caso especial peculiar, agregado por conveniencia, y en mi opinión, nunca debería haber existido, porque invalida la regla que propuse anteriormente.
Hasta donde sé, en ninguna otra parte del lenguaje esta sintaxis es significativa, pero incluso si lo es, apunta a una discrepancia en la forma en que se definen los tipos de puntero en C. En cualquier otro lugar, en declaraciones de una sola variable, en listas de parámetros, en miembros de estructura, etc., puede declarar sus punteros como
type* pointer-variable
lugar de
type *pointer-variable
;
Es perfectamente legal y tiene más sentido.
Para aclarar por qué el tutorial está mal,
int *my_int_ptr = 2;
es una "violación de restricción", es un código que no está permitido compilar y el compilador debe darle un diagnóstico al encontrarlo.
Según 6.5.16.1 Asignación simple:
Restricciones
Uno de los siguientes debe contener:
- el operando izquierdo tiene un tipo aritmético atómico, calificado o no calificado, y el derecho tiene un tipo aritmético;
- el operando izquierdo tiene una versión atómica, calificada o no calificada de una estructura o tipo de unión compatible con el tipo de la derecha;
- el operando izquierdo tiene un tipo de puntero atómico, calificado o no calificado, y (considerando el tipo que tendría el operando izquierdo después de la conversión de valor) ambos operandos son punteros a versiones calificadas o no calificadas de tipos compatibles, y el tipo señalado por la izquierda tiene todo los calificadores del tipo señalado por la derecha;
- el operando izquierdo tiene un tipo de puntero atómico, calificado o no calificado, y (considerando el tipo que tendría el operando izquierdo después de la conversión del valor l) un operando es un puntero a un tipo de objeto, y el otro es un puntero a una versión calificada o no calificada de nulo, y el tipo señalado por la izquierda tiene todos los calificadores del tipo señalado por la derecha;
- el operando izquierdo es un puntero atómico, calificado o no calificado, y el derecho es una constante de puntero nulo; o
- el operando izquierdo tiene un tipo _Bool atómico, calificado o no calificado, y el derecho es un puntero.
En este caso, el operando izquierdo es un puntero no calificado. En ninguna parte menciona que el operando correcto puede ser un entero (tipo aritmético). Entonces el código viola el estándar C.
Se sabe que GCC se comporta mal a menos que explícitamente le digas que sea un compilador de C estándar.
Si compila el código como
-std=c11 -pedantic-errors
, dará un diagnóstico correcto como debe hacerlo.
Solo recuerda:
En C, el valor del lado izquierdo siempre se evalúa en una ubicación de memoria , mientras que el lado derecho siempre se evalúa en un valor (ya sea int o dirección o clase Foo).
este es un puntero nulo
int * nullPtr = (void*) 0;
int *my_int_ptr = 2
almacena el valor entero 2 en cualquier dirección aleatoria que se encuentre en my_int_ptr cuando se asigna.
Esto está completamente mal. Si esto realmente está escrito, obtenga un mejor libro o tutorial.
int *my_int_ptr = 2
define un puntero entero que apunta a la dirección 2. Lo más probable es que se bloquee si intenta acceder a la dirección
2
.
*my_int_ptr = 2
, es decir, sin el
int
en la línea, almacena el valor dos en la dirección aleatoria a la que
my_int_ptr
.
Dicho esto, puede asignar
NULL
a un puntero cuando está definido.
char *x=NULL;
es perfectamente válido C.
Editar: Al escribir esto, no sabía que la conversión de entero a puntero es un comportamiento definido por la implementación. Consulte las buenas respuestas de @MM y @SouravGhosh para obtener más detalles.