[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