Problemas de "puntero de entero/entero de puntero sin conversión"

Resuelto Lundin asked hace 6 años • 1 respuestas

Esta pregunta está destinada a ser una entrada de preguntas frecuentes para todas las cuestiones de inicialización/asignación entre números enteros y punteros.


Quiero escribir código en el que un puntero esté configurado en una dirección de memoria específica, por ejemplo 0x12345678. Pero al compilar este código con el compilador gcc, recibo advertencias/errores de "la inicialización hace que el puntero sea un entero sin conversión":

int* p = 0x12345678;

De manera similar, este código proporciona "la inicialización genera un número entero a partir del puntero sin conversión":

int* p = ...;
int i =  p;

Si hago lo mismo fuera de la línea de declaración de variable, el mensaje es el mismo pero dice "asignación" en lugar de "inicialización":

p = 0x12345678; // "assignment makes pointer from integer without a cast"
i = p;          // "assignment makes integer from pointer without a cast"

Las pruebas con otros compiladores populares también dan mensajes de error/advertencia:

  • clang dice "conversión de entero a puntero incompatible"
  • inticc dice " no se puede utilizar un valor de tipo para inicializar una entidad de tipo int*"
  • MSVC (cl) dice que "la inicialización int*difiere en los niveles de direccionamiento indirecto de int".

Pregunta: ¿Son válidos los ejemplos anteriores en C?


Y una pregunta de seguimiento:

Esto no da ninguna advertencia/error:

int* p = 0;

¿Por qué no?

Lundin avatar Sep 05 '18 20:09 Lundin
Aceptado

No, no es C válido y nunca ha sido C válido. Estos ejemplos son las llamadas violaciones de restricciones del estándar.

El estándar no le permite inicializar/asignar un puntero a un número entero, ni un número entero a un puntero. Debe forzar manualmente una conversión de tipo con una conversión:

int* p = (int*) 0x1234;

int i = (int)p;

Si no utiliza la conversión, el código no es C válido y su compilador no puede dejar pasar el código sin mostrar un mensaje. El operador del elenco afirma: C17 6.5.4/3:

Restricciones
/--/
Las conversiones que implican punteros, salvo las permitidas por las restricciones de 6.5.16.1, se especificarán mediante una conversión explícita.

6.5.16.1 son las reglas de asignación simple que permiten ciertas conversiones implícitas de punteros, ver C17 6.5.16.1 §1:

6.5.16.1 Asignación simple

Restricciones

Se considerará uno de los siguientes:

  • el operando de la izquierda tiene un tipo aritmético atómico, calificado o no calificado, y el de la derecha tiene un tipo aritmético;
  • el operando de la izquierda 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 l) ambos operandos son punteros a versiones calificadas o no calificadas de tipos compatibles, y el tipo al que apunta la izquierda tiene todos los calificativos 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 de valor) 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 de la izquierda tiene el tipo atómico, calificado o no calificado _Bool, y el de la derecha es un puntero.

En el caso de int* p = 0x12345678;, el operando izquierdo es un puntero y el derecho es un tipo aritmético.
En el caso de int i = p;, el operando izquierdo es de tipo aritmético y el derecho es un puntero.
Ninguno de estos encaja con ninguna de las limitaciones citadas anteriormente.

En cuanto a por qué int* p = 0;funciona, es un caso especial. El operando izquierdo es un puntero y el derecho es una constante de puntero nulo . Más información sobre la diferencia entre punteros nulos, constantes de puntero nulo y la macro NULL .


Algunas cosas a tener en cuenta:

  • Si asigna una dirección sin formato a un puntero, es probable que el puntero deba volatilecalificarse, dado que apunta a algo como un registro de hardware o una ubicación de memoria EEPROM/Flash, que puede cambiar su contenido en tiempo de ejecución.

  • De ninguna manera se garantiza que la conversión de un puntero a un número entero funcione incluso con la conversión. El estándar (C17 6.3.2.3 §5 y §6 dice):

Un número entero se puede convertir a cualquier tipo de puntero. Excepto lo especificado anteriormente, el resultado está definido por la implementación, es posible que no esté alineado correctamente, que no apunte a una entidad del tipo al que se hace referencia y que sea una representación de trampa. 68)

Cualquier tipo de puntero se puede convertir a un tipo entero. Excepto lo especificado anteriormente, el resultado está definido por la implementación. Si el resultado no se puede representar en el tipo entero, el comportamiento no está definido. No es necesario que el resultado esté en el rango de valores de ningún tipo entero.

Nota informativa al pie:

68) Las funciones de mapeo para convertir un puntero en un número entero o un número entero en un puntero pretenden ser consistentes con la estructura de direccionamiento del entorno de ejecución.

Además, la dirección de un puntero puede ser mayor que la que cabe dentro de un archivo int, como es el caso de la mayoría de los sistemas de 64 bits. Por lo tanto, es mejor utilizar el uintptr_tde<stdint.h>

Lundin avatar Sep 05 '2018 13:09 Lundin