Problemas de “Puntero de entero/entero de puntero sin conversión”

7 minutos de lectura

avatar de usuario
Lundin

Esta pregunta pretende ser una entrada de preguntas frecuentes para todos los problemas de inicialización/asignación entre números enteros y punteros.


Quiero escribir código donde un puntero se establece en una dirección de memoria específica, por ejemplo 0x12345678. Pero al compilar este código con el compilador gcc, obtengo advertencias/errores de “la inicialización hace que el puntero sea un número entero sin conversión”:

int* p = 0x12345678;

De manera similar, este código da “la inicialización hace un número entero desde el puntero sin conversión”:

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

Si hago lo mismo fuera de la línea de declaración de variables, 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”
  • icc dice “un valor de tipo int no se puede utilizar para inicializar una entidad de tipo int*
  • MSVC (cl) dice “inicializando int* difiere en los niveles de indirección de int“.

Pregunta: ¿Los ejemplos anteriores son C válidos?


Y una pregunta de seguimiento:

Esto no da ninguna advertencia/error:

int* p = 0;

¿Por qué no?

  • la pregunta podría decir explícitamente que “Quiero tener un puntero que apunte a la dirección de memoria 0x12345678” y luego la respuesta podría abordar esto

    – Antti Haapala — Слава Україні

    5 sep 2018 a las 13:56


  • @AnttiHaapala Sí, buena decisión, especialmente porque la respuesta ya insinuaba las mejores prácticas cuando se trata de direcciones de memoria. Agregué una nueva oración en la parte superior de la pregunta.

    – Lundin

    5 de septiembre de 2018 a las 14:02

avatar de usuario
Lundin

No, no es C válido y nunca ha sido C válido. Estos ejemplos se denominan violaciones de restricciones de la norma.

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 un molde:

int* p = (int*) 0x1234;

int i = (int)p;

Si no usa el molde, el código no es C válido y su compilador no puede dejar pasar el código sin mostrar un mensaje. El operador cast afirma: C17 6.5.4/3:

Restricciones

/–/
Las conversiones en las que intervienen punteros, excepto cuando lo permitan las restricciones de 6.5.16.1, se especificarán mediante una conversión explícita.

6.5.16.1 siendo las reglas de tarea sencilla que permiten ciertas conversiones implícitas de punteros, ver C17 6.5.16.1 §1:

6.5.16.1 Asignación simple

Restricciones

Se cumplirá uno de los siguientes:

  • el operando izquierdo tiene tipo aritmético atómico, calificado o no calificado, y el derecho tiene 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 lvalue) 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 lvalue) 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 tipo _Bool atómico, calificado o no calificado, y el de la derecha es un puntero.

En caso de int* p = 0x12345678;el operando izquierdo es un puntero y el derecho es un tipo aritmético.
En caso de int i = p;el operando izquierdo es un 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 un 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 procesar a un puntero, es probable que el puntero deba ser volatile calificado, 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 el molde. El estándar (C17 6.3.2.3 §5 y §6 dice):

Un entero se puede convertir en cualquier tipo de puntero. Salvo que se especifique lo contrario, el resultado está definido por la implementación, es posible que no esté correctamente alineado, que no apunte a una entidad del tipo al que se hace referencia y que sea una representación de captura. 68)

Cualquier tipo de puntero se puede convertir a un tipo entero. Salvo que se especifique lo contrario, el resultado está definido por la implementación. Si el resultado no se puede representar en el tipo entero, el comportamiento no está definido. El resultado no necesita estar 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 están destinadas a ser coherentes con la estructura de direccionamiento del entorno de ejecución.

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

  • Podría agregar algo sobre la nota al pie 67) de C11: Las funciones de mapeo para convertir un puntero en un número entero o un número entero en un puntero están destinadas a ser coherentes con la estructura de direccionamiento del entorno de ejecución. Es decir, una implementación de calidad no solo sacaría los números del sombrero;)

    – Antti Haapala — Слава Україні

    5 de septiembre de 2018 a las 13:58

  • @AnttiHaapala Agregó la nota al pie. Y sí, antes de ANSI tendía a tratar todos los tipos como int. De todos modos, nunca se ha estandarizado. Más preocupante es que algunos compiladores de sistemas integrados semimodernos dejan pasar este tipo de código sin ningún mensaje de diagnóstico. IIRC, algunos compiladores de PC más antiguos como Borland también hicieron esto.

    – Lundin

    5 de septiembre de 2018 a las 14:11

  • Asi que p = 0x10000000; estaría bien (no es una violación de restricción) iff 0x10000000 resultó ser otra implementación específica constante de puntero nulo?

    – chux – Reincorporar a Monica

    5 de septiembre de 2018 a las 14:16

  • “Una expresión constante de entero con el valor 0, o una expresión de este tipo convertida en tipo void *, se denomina constante de puntero nulo”.; se podría agregar que, de port70.net/~nsz/c/c11/n1570.html#6.3.2.3p3

    – Antti Haapala — Слава Україні

    5 de septiembre de 2018 a las 15:18


  • Bueno, puede usar 0LL seguro, o 0ULL o '\0' o (5-5). Pero no 42. Como 42 no es lo que el estándar llama una constante de puntero nulo.

    – Antti Haapala — Слава Україні

    5 sep 2018 a las 15:32


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad