[C con Clase] SDL operaciones bitwise y punteros.

Steven Davidson srd4121 en njit.edu
Vie Feb 25 19:44:43 CET 2011


Hola David,

On 2/25/2011 7:24 AM, David wrote:
> Hola, buenas a todos, estoy siguiendo el tutorial de SDL:

[CORTE]

> Mi primera pregunta es:
> ¿Cómo se interpreta la siguiente sentencia?: *(Uint16 *)p = pixel.
> La primera parte lo entiendo, si el sistema trabaja con una
> profundidad de color de 16 bits o 2 bytes entonces se hace un cast a
> *p para que apunte a variables del tipo Uint16 y así *p en vez de

Técnicamente, no hacemos un cásting a '*p', sino a 'p', el puntero.

> usar un byte o 8 bits para apuntar a el valor, ahora podrá apuntar a
> variables de2 bytes o 16 bits, pero qué pasa con el otro operador de
> indirección "*" el que está de rojo, *(Uint16 *)p, no entiendo a que
> apunta aquí.
>

Apunta al valor que está en esa misma dirección guardada en 'p'. Lo que 
pasa es que ahora conseguirá un valor de 2 bytes en lugar de un valor de 
1 byte, como indicaba 'p', originalmente. El operador * es de 
indirección y por tanto está accediendo al valor apuntado. En otras 
palabras, estamos asignando el valor de 'pixel' a la memoria apuntada 
por 'p'. Esto es similar al caso 1 del 'switch/case':
*p = pixel;

Lo que pasa es que, en el caso de 16 bits, tenemos que hacer un cásting. 
Podríamos haber hecho esto:

Uint16 *p16 = (Uint16 *)p;
*p16 = pixel;

Si quieres, te pongo un vector descriptor, para que veas lo que ocurre 
en memoria. Anterior a esta sentencia, tenemos lo siguiente en memoria:

Dirección
de Memoria	Nombre	Tipo		Valor
--------------------------------------------------
0x1122FF00	pixel	Uint32		0xFF770000  (color RGB)
...
0x11AAEE00	p	Uint8 *		0x33FFCC00
...
0x33FFCC00	----	Uint8		0x00
0x33FFCC01	----	Uint8		0x00
0x33FFCC02	----	Uint8		0x00
0x33FFCC03	----	Uint8		0x00
0x33FFCC04	----	Uint8		0x00
0x33FFCC05	----	Uint8		0x00
...

Después de la asignación, veríamos lo siguiente en memoria:

Dirección
de Memoria	Nombre	Tipo		Valor
--------------------------------------------------
0x1122FF00	pixel	Uint32		0xFF770000  (color RGB)
...
0x11AAEE00	p	Uint8 *		0x33FFCC00
...
0x33FFCC00	----	Uint8		0xFF
0x33FFCC01	----	Uint8		0x77
0x33FFCC02	----	Uint8		0x00
0x33FFCC03	----	Uint8		0x00
0x33FFCC04	----	Uint8		0x00
0x33FFCC05	----	Uint8		0x00
...

El puntero 'p' sigue apuntando al mismo lugar, pero ahora hemos copiado 
el valor en 'pixel' a la memoria apuntada por 'p'. Previamente, tuvimos 
que indicar a C/C++ que tratase 'p' como un puntero a 'Uint16'.

La verdad es que deberíamos hacer un cásting de 'pixel' (de tipo 
'Uin32') al tipo 'Uint16', ya que perderíamos datos y por tanto el 
compilador no puede realizar esta operación implícitamente. Esto es,

*(Uint16 *)p = (Uint16)pixel;

> Mi segunda pregunta es sobre el siguiente bloque:
>
> p[R] = pixel & 0xFF;
> p[G] = (pixel >> 8) & 0xFF;
> p[B] = (pixel >> 16) & 0xFF;
>
> Tratándose de punteros, sé que se pueden hacer cosas como esa, pero
> cómo sabe el compilador que p[1] y p[2], (p[G] y p[B]
> respectivamente) no están ocupadas con información de otra
> aplicación, no es esto peligroso?, manipular direcciones de memoria
> que no se han declarado?

En general, sí. Por eso el programador de C/C++ debe poner especial 
atención y mucho cuidado al usar punteros.

> Sé que declare *p, pero no declaré p[1], ni p[2], ni p[..n].

Creo que estás confundiendo el operador [] con arrays. El operador [] 
puede servir tanto para arrays como para punteros. Por ejemplo,

int lista[10];

lista[0] = 10;
lista[1] = 100;
...

Pero también podemos usar la nomenclatura de punteros:

*lista = 10;
*(lista+1) = 100;
...

Estas operaciones son equivalentes entre sí. También podemos hacer lo 
mismo con punteros:

int *p = lista;

*p = 10;
*(p+1) = 100;
...

o incluso,

int *p = array;

p[0] = 10;
p[1] = 100;
...

La diferencia está en que 'lista' es un array y por tanto el compilador 
creará la memoria necesaria para que "exista". Además, un array no puede 
ser modificado, pero sus elementos sí. Por lo tanto, podemos decir que 
un array es como un puntero constante. Esto es,

lista = new int[100];  // incorrecto: 'lista' es constante
p = new int[100];      // correcto: 'p' apunta a una nueva dirección

Deberías revisar este tema de punteros de C/C++ en la documentación. En 
nuestro curso, puedes dirigirte al capítulo 12: 
http://c.conclase.net/curso/index.php?cap=012b#PUNT_Correspondencia

Por consiguiente, 'p' apunta a una dirección de memoria basada en 
'superficie->pixels'. Al comienzo de esta función, escribes:

Uint8 *p = (Uint8 *)superficie->pixels + y * superficie->pitch + x * bpp;

Por lo tanto, 'p[0]', 'p[1]', etc. son los valores en esas direcciones 
de memoria.

> p[R], p[G], p[B], son de 32 bits o 4 bytes cada uno? O p[R], p[G],
> p[B] representa cada uno un byte de los 4 que tiene p. Me explico con

Para responder a esta pregunta, simplemente mira el tipo del dato 
apuntado por 'p'. Como 'p' es de tipo 'Uint8 *', entonces los valores 
apuntados son de tipo 'Uint8' que seguramente se define como 'unsigned 
char' en alguna parte.

> un esquema:
>
> Recordemos que *p es de 4 bytes y para el caso de profundidad de

No. 'p' es de tipo 'Uint *', así que '*p' es de tipo 'Uint8', que como 
acabo de explicar, es 1 byte.

Lo que seguramente es cierto es que 'p', como puntero, ocupa 32 bits, si 
estamos en una plataforma de 32 bits y por tanto las direcciones de 
memoria son de 32 bits.

>

[CORTE]

> Mi tercera pregunta viene en relación a la siguiente función:
>
> Uint32 GetPixel(SDL_Surface *superficie, int x, int y)

[CORTE]

> El siguiente "return " no lo entiendo bien, alguien me podría
> explicar a nivel de bits que hace lo siguiente:
>
> return p[R] | p[G] << 8 | p[B] << 16;
>
> Porque si p[R], p[G] y p[B] son cada uno de un Byte o 8 bits entonces
> no tiene sentido el desplazamiento de bits.
> O, lo que hace es que: escribe los 8 bits del rojo, luego que
> desplace 8 bits el verde y y escriba el valor en bits del verde y por
> último que desplace 16 bits el azul para que el return devuelva los
> 24 bits como [B, G, R], o sea, una concatenación de bits.

Esta última explicación es la correcta para aclarar tu duda.

En parte, tienes razón en la primera duda que planteas. Los operandos 
son de tipo 'Uint8' y por tanto estos operadores darán resultados de 
tipo 'Uint8'. Sin embargo, el tipo resultante es de 'Uint32', por lo que 
el compilador entiende que el resultado será de 32 bits.

De todas maneras, estoy de acuerdo contigo; esto no sería la forma 
correcta de escribir esta operación. Aconsejaría hacer cástingas a cada 
operando para que las expresiones fueren del tipo correcto. Esto es,

return ((Uint32)p[R]) | ((Uint32)p[G]) << 8 | ((Uint32)p[B]) << 16;

Ciertamente, esto es algo engorroso y poco legible, pero al menos nos 
aseguramos que las expresiones son correctas.

> Aunque lo que yo veo es que se está usando un OR entre los tres.
>

Correcto. Realizamos dos operaciones de OR a nivel de bits. Esto es 
mejor verlo con un ejemplo en binario,

    11001110
OR 00111000
-----------
    11111110

En el caso de la expresión anterior, obtendríamos algo así:

    1100111000000000
OR 0000000000111000
-------------------
    1100111000111000

Básicamente, agregamos una parte a la parte "superior" del resultado y 
la otra parte a la parte "inferior" del resultado. Es casi como 
empaquetar la información en otro tipo de dato - en otro formato. Visto 
de una forma general:

    aaaaaaaa00000000
OR 00000000bbbbbbbb
-------------------
    aaaaaaaabbbbbbbb

donde 'a' y 'b' son cualesquier bits.

Te aconsejo que consultes el capítulo 18 de nuestro curso de C++, yendo 
a: http://c.conclase.net/curso/index.php?cap=018#inicio


Espero que todo esto te aclare las dudas.

Steven





Más información sobre la lista de distribución Cconclase