Pregunta Punteros a punteros vs. punteros normales


El propósito de un puntero es guardar la dirección de una variable específica. Entonces la estructura de memoria del siguiente código debería verse así:

int a = 5;
int *b = &a;

...... dirección de la memoria ...... valor
  a ... 0x000002 ................... 5
  b ... 0x000010 ................... 0x000002

Bien vale. Entonces supongamos que ahora quiero guardar la dirección del puntero * b. Entonces generalmente definimos un doble puntero, ** c, como

int a = 5;
int *b = &a;
int **c = &b;

Entonces la estructura de la memoria se ve así:

...... dirección de la memoria ...... valor
  a ... 0x000002 ................... 5
  b ... 0x000010 ................... 0x000002
  c ... 0x000020 ................... 0x000010

Entonces ** c refiere la dirección de * b.

Ahora mi pregunta es, ¿por qué este tipo de código,

int a = 5;
int *b = &a;
int *c = &b;

generar una advertencia?

Si el propósito del puntero es solo guardar la dirección de la memoria, creo que no debería haber jerarquía si la dirección que vamos a guardar se refiere a una variable, un puntero, un puntero doble, etc., por lo que el siguiente tipo de código debería Sé valido.

int a = 5;
int *b = &a;
int *c = &b;
int *d = &c;
int *e = &d;
int *f = &e;

74
2018-06-28 13:01


origen


Respuestas:


En

int a = 5;
int *b = &a;   
int *c = &b;

Recibes una advertencia porque &b es de tipo int **e intenta inicializar una variable de tipo int *. No hay conversiones implícitas entre esos dos tipos, lo que lleva a la advertencia.

Para tomar el ejemplo más largo que desea trabajar, si tratamos de eliminar la referencia f el compilador nos dará una int, no un puntero que podamos desreferencia adicional.

También tenga en cuenta que en muchos sistemas int y int* no son del mismo tamaño (por ejemplo, un puntero puede tener 64 bits de largo y una int 32 bits de largo). Si desreferencia f y obtener un int, pierdes la mitad del valor, y luego ni siquiera puedes convertirlo en un puntero válido.


91
2018-06-28 13:04



Si el propósito del puntero es solo guardar la dirección de la memoria, creo   no debería haber jerarquía si la dirección que vamos a guardar   se refiere a variable, puntero, doble puntero, ... etc.

En tiempo de ejecución, sí, un puntero solo contiene una dirección. Pero en tiempo de compilación también hay un tipo asociado con cada variable. Como los otros han dicho, int* y int** son dos tipos diferentes e incompatibles.

Hay un tipo, void*, que hace lo que desea: almacena solo una dirección, puede asignarle cualquier dirección:

int a = 5;
int *b = &a;
void *c = &b;

Pero cuando quieres desreferenciar un void*, debe proporcionar la información del tipo "perdido" usted mismo:

int a2 = **((int**)c);

54
2018-06-28 13:30



Ahora mi pregunta es, ¿por qué este tipo de código,

int a = 5; 
int *b = &a; 
int *c = &b; 

generar una advertencia?

Debes volver a los fundamentos.

  • variables tienen tipos
  • las variables tienen valores
  • un puntero es un valor
  • un puntero se refiere a una variable
  • Si p es un valor de puntero entonces *p es una variable
  • Si v es una variable entonces &v es un puntero

Y ahora podemos encontrar todos los errores en su publicación.

Entonces asuma que ahora quiero guardar la dirección del puntero *b

No. *b es una variable de tipo int. No es un puntero. b es una variable cuya valor es un puntero. *b es un variable cuyo valor es un número entero.

**c se refiere a la dirección de *b.

NO NO NO. Absolutamente no. Tú tener para entender esto correctamente si vas a entender los punteros.

*b es una variable; es un alias para la variable a. La dirección de la variable a es el valor de la variable b. **c no se refiere a la dirección de a. Más bien, es un variable eso es un alias para variable a. (Y tambien *b.)

La declaración correcta es: el valor de variable c es el dirección de b. O, de manera equivalente: el valor de c es un puntero que se refiere a b.

Cómo sabemos esto? Regresa a los fundamentos. Tú dijiste eso c = &b. Entonces, ¿cuál es el valor de c? Un puntero. ¿A qué? b.

Asegúrese completamente entender las reglas fundamentales

Ahora que esperas comprender la relación correcta entre las variables y los indicadores, deberías poder responder a tu pregunta sobre por qué tu código da un error.


23
2018-06-28 20:51



El sistema de tipos de C requiere esto, si desea obtener una advertencia correcta y si desea que el código compile en absoluto. Con solo un nivel de profundidad de punteros, no sabría si el puntero apunta a un puntero oa un número entero real.

Si desreferencia un tipo int** sabes que el tipo que obtienes es int* y de manera similar si desreferencia int* el tipo es int. Con su propuesta, el tipo sería ambiguo.

Tomando de su ejemplo, es imposible saber si c señala a un int o int*:

c = rand() % 2 == 0 ? &a : &b;

¿A qué tipo apunta c? El compilador no lo sabe, por lo que esta próxima línea es imposible de realizar:

*c;

En C, toda la información de tipo se pierde después de la compilación, ya que cada tipo se verifica en tiempo de compilación y ya no es necesario. Su propuesta en realidad desperdiciaría la memoria y el tiempo, ya que cada apuntador debería tener información de tiempo de ejecución adicional sobre los tipos que figuran en los punteros.


20
2018-06-28 13:06



Los punteros son abstracciones de direcciones de memoria con semántica de tipo adicional, y en un lenguaje como el tipo C importa.

En primer lugar, no hay garantía de que int * y int ** tienen el mismo tamaño o representación (en arquitecturas modernas de escritorio lo hacen, pero no se puede confiar en que sea universalmente cierto).

En segundo lugar, el tipo es importante para la aritmética del puntero. Dado un puntero p de tipo T *, la expresion p + 1 cede la dirección del próximo objeto de tipo T. Entonces, asuma las siguientes declaraciones:

char  *cp     = 0x1000;
short *sp     = 0x1000;  // assume 16-bit short
int   *ip     = 0x1000;  // assume 32-bit int
long  *lp     = 0x1000;  // assume 64-bit long

La expresion cp + 1 nos da la dirección del siguiente char objeto, que sería 0x1001. La expresion sp + 1 nos da la dirección del siguiente short objeto, que sería 0x1002. ip + 1 Nos da 0x1004y lp + 1 Nos da 0x1008.

Entonces, dado

int a = 5;
int *b = &a;
int **c = &b;

b + 1 nos da la dirección del siguiente inty c + 1 nos da la dirección del siguiente puntero a int.

Los punteros a punteros son necesarios si desea que una función escriba en un parámetro del tipo de puntero. Toma el siguiente código:

void foo( T *p )    
{
  *p = new_value(); // write new value to whatever p points to
}

void bar( void )
{
  T val;
  foo( &val );     // update contents of val
}

Esto es cierto para cualquier tipo T. Si reemplazamos T con un tipo de puntero P *, el código se convierte

void foo( P **p )    
{
  *p = new_value(); // write new value to whatever p points to
}

void bar( void )
{
  P *val;
  foo( &val );     // update contents of val
}

La semántica es exactamente la misma, solo son los tipos que son diferentes; el parámetro formal psiempre hay un nivel más de indirección que la variable val.


17
2018-06-28 14:41



Creo que no debería haber jerarquía si la dirección que vamos a guardar se refiere a variable, puntero, doble puntero

Sin la "jerarquía" sería muy fácil generar UB por todas partes sin ninguna advertencia, eso sería horrible.

Considera esto:

char c = 'a';
char* pc = &c;
char** ppc = &pc;
printf("%c\n", **ppc);   // compiles ok and is valid
printf("%c\n", **pc);    // error: invalid type argument of unary ‘*’

El compilador me da un error y por lo tanto, me ayuda a saber que he hecho algo mal y puedo corregir el error.

Pero sin "jerarquía", como:

char c = 'a';
char* pc = &c;
char* ppc = &pc;
printf("%c\n", **ppc);   // compiles ok and is valid
printf("%c\n", **pc);    // compiles ok but is invalid

El compilador no puede dar ningún error ya que no hay una "jerarquía".

Pero cuando la línea:

printf("%c\n", **pc);

ejecuta, es UB (comportamiento indefinido).

primero *pc lee el char como si fuera un puntero, es decir, probablemente lea 4 u 8 bytes, aunque solo reservamos 1 byte. Eso es UB.

Si el programa no se bloqueó debido a la UB anterior, pero acaba de devolver algún valor garbish, el segundo paso sería desreferenciar el valor garbish. Una vez más, UB.

Conclusión

El sistema de tipos nos ayuda a detectar errores al ver int *, int **, int ***, etc. como diferentes tipos.


11
2018-06-28 13:56



Si el propósito del puntero es solo guardar la dirección de la memoria, creo que no debería haber jerarquía si la dirección que vamos a guardar se refiere a la variable, el puntero, el puntero doble, etc., por lo que el tipo de código a continuación debería ser válido.

Creo que este es su malentendido: el objetivo del puntero es almacenar la dirección de la memoria, pero un puntero generalmente también tiene un tipo para que sepamos qué esperar en el lugar al que apunta.

Especialmente, a diferencia de usted, otras personas realmente desean tener este tipo de jerarquía para saber qué hacer con los contenidos de la memoria señalados por el puntero.

Es el mismo punto del sistema de puntero de C tener información de tipo asociada.

Si lo haces

int a = 5;

&a implica que lo que obtienes es un int * de modo que si desreferencia es un int de nuevo.

Llevando eso a los siguientes niveles,

int *b = &a;
int **c = &b;

&b es un puntero también. Pero sin saber qué se esconde detrás de él, resp. lo que apunta, es inútil. Es importante saber que desreferenciando un puntero revela el tipo del tipo original, de modo que *(&b) es un int *y **(&b) es el original int valor con el que trabajamos

Si considera que en sus circunstancias no debería existir una jerarquía de tipos, siempre puede trabajar con void *, aunque la usabilidad directa es bastante limitada.


10
2018-06-28 13:09