¿Cuáles son las reglas para lanzar punteros en C?

10 minutos de lectura

avatar de usuario
Theo Chronic

K&R no lo pasa por alto, pero lo usa. Traté de ver cómo funcionaría escribiendo un programa de ejemplo, pero no funcionó tan bien:

#include <stdio.h> 
int bleh (int *); 

int main(){
    char c="5"; 
    char *d = &c;

    bleh((int *)d); 
    return 0;  
}

int bleh(int *n){
    printf("%d bleh\n", *n); 
    return *n; 
}

Se compila, pero mi declaración de impresión escupe variables basura (son diferentes cada vez que llamo al programa). ¿Algunas ideas?

  • int tiene un tamaño mayor que char, por lo que lee más allá del espacio del carácter ‘5’. Intente hacer lo mismo usando un tipo de datos más pequeño (int c, printf “%c”)

    – HojaJS

    23 de junio de 2013 a las 11:59

  • El valor de *n será un intque debe ser de 4 bytes. *n apunta a la variable local c en main(). Esto significa que estarás escribiendo el valor de 'c' y los tres bytes que le siguen en la memoria. (Mi conjetura es el valor de d.) Puede verificar esto escribiendo el número en hexadecimal; dos de los dígitos deben ser iguales cada vez.

    – milimoose

    23/06/2013 a las 12:00

  • '5' — podrías pensar que esto parece un int ya que parece ser un número, pero es solo un carácter que representa el dígito 5.

    – mah

    23 de junio de 2013 a las 12:17

  • Ejecuté la misma prueba en mi máquina (gcc, x86_64) y no obtuve errores de compilación, y el programa funciona bien cada vez (sin basura). Pero no hice nada diferente al OP. Extraño.

    – Andy J.

    20 de junio de 2014 a las 3:41

  • Cualquiera que lea esta respuesta debería mirar la respuesta de R. a continuación

    – polynomial_donut

    13 de agosto de 2018 a las 18:42

Al pensar en punteros, ayuda dibujar diagramas. Un puntero es una flecha que apunta a una dirección en la memoria, con una etiqueta que indica el tipo de valor. La dirección indica dónde buscar y el tipo indica qué llevar. Lanzar el puntero cambia la etiqueta en la flecha pero no donde apunta la flecha.

d en main es un puntero a c que es de tipo char. A char es un byte de memoria, así que cuando d está desreferenciado, obtienes el valor en ese byte de memoria. En el siguiente diagrama, cada celda representa un byte.

-+----+----+----+----+----+----+-
 |    | c  |    |    |    |    | 
-+----+----+----+----+----+----+-
       ^~~~
       | char
       d

cuando lanzas d para int*estas diciendo eso d realmente apunta a un int valor. En la mayoría de los sistemas actuales, un int ocupa 4 bytes.

-+----+----+----+----+----+----+-
 |    | c  | ?₁ | ?₂ | ?₃ |    | 
-+----+----+----+----+----+----+-
       ^~~~~~~~~~~~~~~~~~~
       | int
       (int*)d

Cuando desreferencias (int*)d, obtiene un valor que se determina a partir de estos cuatro bytes de memoria. El valor que obtenga depende de lo que esté marcado en estas celdas ?y sobre cómo un int se representa en la memoria.

una computadora es little-endianlo que significa que el valor de un int se calcula de esta manera (suponiendo que abarca 4 bytes):
* ((int*)d) == c + ?₁ * 2⁸ + ?₂ * 2¹⁶ + ?₃ * 2²⁴. Entonces verá que si bien el valor es basura, si imprime en hexadecimal (printf("%x\n", *n)), los dos últimos dígitos siempre serán 35 (ese es el valor del personaje '5').

Algunos otros sistemas son big-endian y organizan los bytes en la otra dirección: * ((int*)d) == c * 2²⁴ + ?₁ * 2¹⁶ + ?₂ * 2⁸ + ?₃. En estos sistemas, encontrará que el valor siempre empieza con 35 cuando se imprime en hexadecimal. Algunos sistemas tienen un tamaño de int eso es diferente de 4 bytes. Unos pocos sistemas raros organizan int de diferentes maneras, pero es muy poco probable que los encuentres.

Dependiendo de su compilador y sistema operativo, puede encontrar que el valor es diferente cada vez que ejecuta el programa, o que siempre es el mismo pero cambia cuando realiza ajustes menores al código fuente.

En algunos sistemas, un int El valor debe almacenarse en una dirección que sea un múltiplo de 4 (o 2 u 8). Esto se llama un alineación requisito. Dependiendo de si la dirección de c esté correctamente alineado o no, el programa puede fallar.

En contraste con su programa, esto es lo que sucede cuando tiene un int valor y tomar un puntero a él.

int x = 42;
int *p = &x;
-+----+----+----+----+----+----+-
 |    |         x         |    | 
-+----+----+----+----+----+----+-
       ^~~~~~~~~~~~~~~~~~~
       | int
       p

el puntero p apunta a un int valor. La etiqueta de la flecha describe correctamente lo que hay en la celda de memoria, por lo que no hay sorpresas al eliminar la referencia.

  • Buena descripción. Me gustaría señalar/discutir que en la mayoría de las computadoras puede ser cierto que int es un valor de 32 bits, pero para otros ingenieros integrados, int es generalmente 16 bits y muestra lo útil y, probablemente, importante que es usar uint16_t, uint32_t, int32_t, etc. 🙂

    – Di Bosco

    1 de febrero de 2017 a las 10:28


  • “…los dos últimos dígitos siempre serán 35 (ese es el valor del carácter ‘5’)”. ¿Por qué?

    – Kenny Worden

    09/04/2017 a las 22:59


  • Hola Gilles, cuando probé el código aquí char *a = "abcd"; int *i = (int *)a; printf("%x\n", *i); la salida es 64636261, pero creo que debería ser 61626364. ¿Significa esto que la memoria dentro de este bloque int se lee de atrás hacia adelante?

    – Sol de verano

    29 de junio de 2018 a las 6:05

  • @SummerSun ¿Por qué crees que debería ser 61626364? Si tiene una máquina little-endian (todas las PC son little-endian), sería 64636261. Esto no tiene nada que ver con el orden en que se lee la memoria. Un int probablemente se lea en una sola instrucción de todos modos. Se trata de cómo un bloque de 4 bytes se interpreta como un int valor.

    – Gilles ‘SO- deja de ser malvado’

    29 de junio de 2018 a las 6:21

  • @Malcolm Es un comportamiento indefinido. La desreferenciación del resultado de la conversión es UB (por ejemplo, es posible que no esté alineado correctamente), e incluso la mera construcción de un puntero suele ser UB si la desreferenciación sería UB (creo que las únicas excepciones son los punteros de función y los punteros al final de una matriz). Hay un caso en el que se define el comportamiento, que es si el puntero era originalmente un int* puntero; cualquier puntero de datos se puede convertir a unsigned char* y de vuelta, y creo unsigned char * se puede lanzar a char * y vuelta

    – Gilles ‘SO- deja de ser malvado’

    15/09/2019 a las 21:43

avatar de usuario
Jack

char c="5"

A char (1 byte) se asigna en la pila en la dirección 0x12345678.

char *d = &c;

Obtienes la dirección de c y almacenarlo en dentonces d = 0x12345678.

int *e = (int*)d;

Obligas al compilador a asumir que 0x12345678 apunta a un intpero un int no es solo un byte (sizeof(char) != sizeof(int)). Puede ser de 4 u 8 bytes según la arquitectura o incluso otros valores.

Entonces, cuando imprime el valor del puntero, el número entero se considera tomando el primer byte (que fue c) y otros bytes consecutivos que están en la pila y que son solo basura para su intención.

  • Otros bytes consecutivos no son basura, sino el valor de des decir 0x12345678 en tu ejemplo

    – Kane

    23 de junio de 2013 a las 12:07

  • d no es lo suficientemente grande para sostener 0x12345678

    – Una persona

    1 de enero de 2014 a las 2:33


  • @APerson ¿Por qué es eso?

    – yyny

    14 de enero de 2018 a las 22:24

  • carcter c[] = “5”; carbonizarse re = c; int *e = (int)D; printf(“%p\n”, e);

    – Martian2049

    14/09/2018 a las 15:13

  • Esto es, de hecho, UB: wiki.sei.cmu.edu/confluence/display/c/…

    – OsoAqua

    3 oct 2019 a las 17:56

La conversión de punteros generalmente no es válida en C. Hay varias razones:

  1. Alineación. Es posible que, debido a consideraciones de alineación, el tipo de puntero de destino no pueda representar el valor del tipo de puntero de origen. Por ejemplo, si int * estaban inherentemente alineados en 4 bytes, emitiendo char * para int * perdería los bits inferiores.

  2. Alias. En general, está prohibido acceder a un objeto excepto a través de un lvalue del tipo correcto para el objeto. Hay algunas excepciones, pero a menos que las entiendas muy bien, no querrás hacerlo. Tenga en cuenta que el alias solo es un problema si realmente elimina la referencia del puntero (aplicar el * o -> operadores a él, o pasarlo a una función que lo desreferenciará).

Los principales casos notables en los que está bien lanzar punteros son:

  1. Cuando el tipo de puntero de destino apunta al tipo de carácter. Se garantiza que los punteros a los tipos de caracteres puedan representar cualquier puntero a cualquier tipo y, si se desea, devolverlo con éxito al tipo original. Puntero para anular (void *) es exactamente lo mismo que un puntero a un tipo de carácter, excepto que no se le permite desreferenciarlo o hacer operaciones aritméticas con él, y se convierte automáticamente hacia y desde otros tipos de punteros sin necesidad de conversión, por lo que los punteros a void suelen ser preferibles sobre punteros a tipos de caracteres para este propósito.

  2. Cuando el tipo de puntero de destino es un puntero a tipo de estructura cuyos miembros coinciden exactamente con los miembros iniciales del tipo de estructura al que apunta originalmente. Esto es útil para varias técnicas de programación orientada a objetos en C.

Algunos otros casos oscuros están técnicamente bien en términos de requisitos de idioma, pero son problemáticos y es mejor evitarlos.

  • ¿Puedes vincular a un documento oficial con estos casos oscuros?

    –Eric

    5 de abril de 2017 a las 14:13

  • He visto código en algunos lugares que toma un char* y lo convierte en algún otro puntero, digamos int. Por ejemplo, transmitir valores RGB desde una cámara o bytes fuera de la red. ¿Su referencia significa que ese código no es válido? ¿Es suficiente alinear los datos para que el código sea correcto, o es solo que nuestros compiladores comunes son indulgentes con este uso?

    –Evan ​​Benn

    14 de diciembre de 2017 a las 11:06


  • @EvanBenn: Posiblemente. Si el tampón se obtiene por mallocy almacena datos en él byte a través de fread o similar, siempre que los desplazamientos estén adecuadamente alineados (en general, esto puede ser difícil de determinar, pero ciertamente es cierto si son múltiplos del tamaño de letra) debería ser conforme para convertir al tipo de puntero apropiado y acceder al datos como ese tipo. Pero si está trabajando con un búfer cuyo tipo real es char[N] o algo así, no es válido.

    – R.. GitHub DEJA DE AYUDAR A ICE

    14 de diciembre de 2017 a las 17:26

Sospecho que necesitas una respuesta más general:

¡No hay reglas para lanzar punteros en C! El idioma le permite lanzar cualquier puntero a cualquier otro puntero sin comentarios.

Pero la cosa es: ¡No hay conversión de datos o lo que sea! Es su exclusiva responsabilidad que el sistema no malinterprete los datos después de la transmisión, lo que generalmente sería el caso, lo que provocaría un error de tiempo de ejecución.

Por lo tanto, cuando emita, depende totalmente de usted tener cuidado de que si los datos se utilizan desde un puntero emitido, ¡los datos son compatibles!

C está optimizado para el rendimiento, por lo que carece de reflexividad en tiempo de ejecución de punteros/referencias. Pero eso tiene un precio: usted, como programador, debe cuidar mejor lo que está haciendo. Tienes que saber por ti mismo si lo que quieres hacer es “legal”

avatar de usuario
gkovacs90

Tienes un puntero a un char. Entonces, como su sistema sabe, en esa dirección de memoria hay una char valor en sizeof(char) espacio. Cuando lo lanzas a int*trabajarás con datos de sizeof(int)por lo que imprimirá su carácter y algo de basura de memoria después de él como un número entero.

¿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