Sobre las conversiones “sucias” de C en 64 bits

Esta entrada viene por un comentario realizado por ferdy en un comentario anterior.

Para comprobar si lo que dije funcionaba, he hecho el siguiente programa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio .h>
#include <stdlib .h>
 
#define SIZEOF(x) printf( "sizeof( %s ) = %d Bytes (%d bits)\n", #x, sizeof(x), sizeof(x)*8 )
int main( void )
{
        void *ptr;
        int valor;
 
        /* Compruebo que se pueda pasar de entero sin signo a puntero.
        * A grandes rasgos consiste en hacer creer al compilador que el
        * entero es una dirección de memoria. */
 
        valor = 5;
        printf( "Valor inicial antes de la transformacion: %d\n", valor );
        ptr = (void *)valor;
        valor = (int)ptr;
        printf( "Valor tras la transformacion: %d\n", valor );
 
        /* Compruebo que se pueda pasar de entero con signo a puntero.
        * A grandes rasgos consiste en hacer creer al compilador que el
        * entero es una dirección de memoria. */
 
        valor = -5;
        printf( "Valor inicial antes de la transformacion: %d\n", valor );
        ptr = (void *)valor;
        valor = (int)ptr;
        printf( "Valor tras la transformacion: %d\n", valor );
 
        /* Compruebo los tamaños de los distintos tipo de datos. El tipo 'void *'
        * es un puntero. Todos los puntero tienen el mismo tamaño en memoria,
        * por lo que no pruebo todas las combinaciones. */
 
        SIZEOF(void *);
        SIZEOF(char );
        SIZEOF(short int);
        SIZEOF(int );
        SIZEOF(long int);
        SIZEOF(float);
        SIZEOF(double);
        SIZEOF(long double);
        return 0;
}
</stdlib></stdio>

No aconsejo a nadie utilizar este tipo de conversiones de tipos, ya que pueden dar problemas. Además, hacen que el código sea menos legible.

El resultado del código anterior sobre un procesador AMD Turion ML-28 con Debian Sid pure 64 es el siguiente:


Valor inicial antes de la transformacion: 5
Valor tras la transformacion: 5
Valor inicial antes de la transformacion: -5
Valor tras la transformacion: -5
sizeof( void * ) = 8 Bytes (64 bits)
sizeof( char ) = 1 Bytes (8 bits)
sizeof( short int ) = 2 Bytes (16 bits)
sizeof( int ) = 4 Bytes (32 bits)
sizeof( long int ) = 8 Bytes (64 bits)
sizeof( float ) = 4 Bytes (32 bits)
sizeof( double ) = 8 Bytes (64 bits)
sizeof( long double ) = 16 Bytes (128 bits)

De este modo podemos ver que se podría, en teoría, convertir cualquier tipo de dato en puntero y almacenarlo en este (a excepción del tipo ‘long double’).

Como recomendación, es preferible realizar asignación dinámica de memoria para almacenar el entero (que es, supongo, la propuesta de ferdy) y luego transformar el puntero a entero a puntero genérico.

Con esto creo que ya está todo, pero si creéis que falta algo, decidlo.

10 comentarios en “Sobre las conversiones “sucias” de C en 64 bits

  1. Lo ideal (y no es dificil) es no hacer suposiciones sobre el tamaño de los tipos de datos más allá de lo que dice el estándar:

    [ por lo que recuerdo ]

    sizeof(char) void* -> T*

    Otro tipo de conversiones no están permitidas:

    T* -> void* -> P* (conversión ilegal)

    Con lo cual ‘se podría, en teoría, convertir cualquier tipo de dato en puntero y almacenarlo en este’ es una afirmación fundamentalmente errónea. De hecho NO NECESITAS hacer estas cosas.

    Por ejemplo, yo no utilicé asignación dinámica de memoria para ‘arreglar’ el otro ejemplo, si tu tienes una función que, por ejemplo, recibe dos punteros genéricos:

    T f( void *a, void *b );

    Y le quieres pasar dos int tales que:

    int a = 4;
    int b = 7;

    Lo que debes hacer es:

    f( &a, &b )

    Y no:

    f(a, b) (error de conversión de tipos)

    De ahí que mi solución fuera crear un par de punteros para hacer la cadena int* -> void* -> int* permitida por el estándar:

    void f( void *a ) {
    int *pa = a;
    int n = *pa;

    /* ya puedes usar n sin problemas
    no hay suposiciones sobre los tamaños */
    }

    void g( void ) {
    int a = 3;

    f( &a );
    }

    Espero que esto aclare las cosas un poquito más. Esto quizá sea carne de cañón para un curso… ¿quién sabe? :)

    Un Saludo.

    – ferdy

  2. Y se ha comido un párrafo al principio, donde explicaba qué cosas permite el estándar y qué cosas no.

    ¿Samuel puedes arreglarlo? Gracias y perdona por el flood :)

    – ferdy

  3. El parrafo ese no lo encuentro :(. Vamos, que se lo ha comido del todo. Aunque no me estraña, es hora de comer :P

    Sobre lo del código descolocado… Bueno, ya trataré de solucionarlo (a ese, espero llegar).

    Sobre lo de dar un curso… Se estaba planteando preparar cursos monográficos de java y no me acuerdo de qué más. Había gente interesada en dar de C, así que sí, es una buena idea, da para un curso y parece que hay gente dispuesta a colaborar.

  4. Ahora a otro asunto.

    Cierto es que mi forma de programa (aunque no lo digas) es un poco guarra (sobre todo si programo para mi). Por eso ese código.

    C permite hacer muchas cosas, alguna de ellas son un poco… cómo decirlo, feas. Un ejemplo, es lo que yo he hecho.

    Es verdad, que como propones, se puede tomar la dirección de la variable para pasarla, y que así no da problemas. Esta solución, que es la más correcta tiene un inconveniente si lo utilizas con listas (vamos, lo que hacía en mi anterior artículo): no funciona.

    La razón de la anterior afirmación reside en el hecho de que si tratas de almacenar en una lista la dirección de una variable local o global, si luego sobreescribes su valor, lo pierdes también en la lista. Por esta razón, lo hice de este otro modo (vale, puede hacerse una lista un poco más sofisticada para que funcione).

    La verdad, sobre el tema de punteros en C creo que se podrían escribir muchos libros. Sobre todo, porque nunca se llegan a dominar del todo y porque son una gran fuente de fallos.

    Si se me ocurre algo más, ya lo pondré.

  5. Ok, reescribo el párrafo en ‘román paladino’ para que no se lo coma tu weblog :P

    Los siguientes tipos de datos están ordenados según tamaños, el orden es ‘menor o igual’ de izquierda a derecha:

    1) char short int long
    2) float double

    Sobre los tamaños de los punteros no puede asumirse nada ESTRICTAMENTE hablando, sin embargo cualquier sistema actual cumple que:

    sizeof(T*) = sizeof(void*)

    Lo que si requiere el estándar es que la siguiente cadena de conversiones sea posible:

    T* a void* a T*

    Sin embargo

    T* a void* a P* es una conversión ilegal.

    En tu caso anterior el paso de los valores estaba bien, lo que está mal es la conversion en la funcion CompararEnteros. Tu tienes:

    T f(void *e1, void *e2)
    {
    int a = (int)e1;
    int b = (int)e2;

    /* …. */
    }

    Ahí tienes una conversion de ‘void*’ a ‘int’ que puedes solucionar aśi:

    T f(void *e1, void *e2)
    {
    int *pa = e1;
    int *pb = e2;
    int a = *pa;
    int b = *pb;

    /* … */
    }

    Y no necesitas castings. Los castings en C deberían ser MUY POCO PROBABLES. Otra cosa que podrías hacer sería:

    T f(void *e1, void *e2)
    {
    int a = *(int *)e1;
    int b = *(int *)e2;

    /* … */
    }

    Las dos formas son igual de válidas, y cualquier compilador generará el mismo código para ambas si en el primer caso no vuelves a usar ‘pa’ y ‘pb’.

    Espero que esto haya dejado más claras aún las cosas :)

    [ PD: Si quereis que de un curso de C, no hay más que decirlo. ]

    [ PD2: Espero que no se coma nada :/ ]

    – ferdy

  6. Pero una cosa, los estandares no estaban para no seguirlos, vamos, lo dice Bill Gates :P

    Bueno, eso no creo que sea cosa tanto del estandar (sino, C lo comprobaría) sino de recomendaciones. Como ya he dicho, en C puedes hacer muchas burradas.

  7. Ah, y de hecho si que lo comprueba. Simplemente tienes que activar las flags necesarias en tu compilador. Aunque no aseguran que el código sea 100% ANSI C, -pedantic y -Wall ayudan mucho.

    – ferdy

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *