Sí, cuando dije que estaba ansioso por ver como tratarías todo el tema de los punteros y la memoria dinámica no lo dije como si toda esta introducción me aburriera;
Ya lo sé, no te hagás problema.
Además de esa forma se puede investigar sobre cómo representa C cada tipo de datos.
Esto puede depender enormemente del
(1) compilador usado, (2) las opciones de compilación elegidas, (3) la versión del sistema operativo en que corre, (4) la máquina en que se está ejecutando, etc. ( ¿"etc." == "\emptyset"?
).
O sea que puede haber un error al creer que vamos a descubrir lo que hace C al llevar a cabo operaciones de bajo nivel.
Me refiero a que "C" es algo abstracto, definido por un comité de personas sobre papel, firmado y sellado.
Es lo que llamamos el "estándar".
Si el estándar
decide que las operaciones de bits, por ejemplo, tienen determinada representación, entonces se supone que todos los compiladores que respetan el estándar tienen que hacer lo mismo.
Lo que podemos esperar ya está definido de antemano en este caso, y no hace falta programar nada... en teoría.
Hacer el programa nos ayudaría a entender si nuestro compilador respeta el estándar o no, o si está correctamente configurado o no, entre otras cuestiones.
Si el estándar no dice nada al respecto, y lo deja indefinido, entonces investigar la representación interna de los bits de datos no puede darnos información certera de lo que podemos esperar de C, porque esto depende de todos los factores que hemos mencionado arriba, y queda abierta la posibilidad de que cada persona obtenga distintos resultados (probablemente) en sus respectivos sistemas.
Sobre este punto estoy teniendo el cuidado de diferenciar hasta dónde el estándar asegura tal o cual cosa, y a partir de dónde es que ya no asegura nada, y las decisiones las toma el compilador que nosotros usamos.
En tu frase:
se puede investigar sobre cómo representa C cada tipo de datos.
Lo correcto sería decir "
se puede investigar sobre cómo representa el compilador GCC cada tipo de datos".
En la universidad lo que enseñan es a razonar algoritmos, y el lenguaje es tema secundario, lo cual está bien, porque uno tiene que aprender a pensar los problemas informáticos en forma independiente al lenguaje que está usando.
Estudiar un lenguaje por sí mismo es un gustito que uno puede darse en su casa.
Está permitido con lenguajes importantes como el C.
No sé si vale la pena hacer el mismo análisis con la mayoría de otros lenguajes.
El análisis técnico de un lenguaje es para entender las limitaciones que tiene, y hacer una programación más profesional, en la que los errores de ejecución no nos agarren por sorpresa, como les pasa todo el tiempo a los programadores del Windows.
Ya que estás con la representación interna de los distintos tipos de datos, como sugerencia nada más, podrías explicar algo sobre las máscaras de bits en C y en general sobre los operadores bit a bit.
Ahora estoy con los tipos de datos, sin analizar los operadores, que son muchos.
Pero me parece muy bien la sugerencia, seguramente le dedicaré sus buenos capítulos.
En cuanto a tu programa:
#include <stdio.h>
int main(){
int k,n;
n=-2;
for(k=31; k>=0; k=k-1)
printf("%d",(n & (1 << k)) >> k);
return 0;
}
y muestra:
-11111111111111111111111111111110
Cambié la línea del printf así:
printf(
"%d ",(n & (1
u << k)) >> k);
(Agregué un espacio en blanco para que se vean mejor los supuestos "bits", y agregué un sufijo "u" al 1).
Con eso parece funcionar bien. Lo que hago es forzar a que la constante 1 sea de tipo
unsigned int, y así los corrimientos de bits "conservan el signo".
Creo que el problema podía estar en el tipo de datos que automáticamente se asigna a los números enteros.
Según las reglas de asignación de tipos, el 1 "intenta" ser un
signed int, si es que cabe ahí.
Como cabe, lo pone de ese tipo.
Después, la operación (1 << k) conserva el tipo de datos, porque es sólo un corrimiento de bits.
El resultado será un entero positivo para k < 31 porque el bit de signo sigue siendo 0.
Mientras que para k = 31, el "1" ya ha llegado al bit de signo, el primero de todos: 1000000000...
En ese caso, como se trata de un signed int, el valor que toma es el de un número negativo, el más negativo de todos, digamos.
El código siguiente:
for(k=0; k<=31; k=k+1)
printf("k==%d: (1 << k) == %d\n ",k, (1 << k));
Genera esta salida:
k==0: (1 << k) == 1
k==1: (1 << k) == 2
k==2: (1 << k) == 4
k==3: (1 << k) == 8
k==4: (1 << k) == 16
k==5: (1 << k) == 32
k==6: (1 << k) == 64
k==7: (1 << k) == 128
k==8: (1 << k) == 256
k==9: (1 << k) == 512
k==10: (1 << k) == 1024
k==11: (1 << k) == 2048
k==12: (1 << k) == 4096
k==13: (1 << k) == 8192
k==14: (1 << k) == 16384
k==15: (1 << k) == 32768
k==16: (1 << k) == 65536
k==17: (1 << k) == 131072
k==18: (1 << k) == 262144
k==19: (1 << k) == 524288
k==20: (1 << k) == 1048576
k==21: (1 << k) == 2097152
k==22: (1 << k) == 4194304
k==23: (1 << k) == 8388608
k==24: (1 << k) == 16777216
k==25: (1 << k) == 33554432
k==26: (1 << k) == 67108864
k==27: (1 << k) == 134217728
k==28: (1 << k) == 268435456
k==29: (1 << k) == 536870912
k==30: (1 << k) == 1073741824
k==31: (1 << k) == -2147483648
Podría ser alguna cuestión de la función printf (pero no, porque printf imprime correctamente datos de tipo signed int), así que podemos verificar el signo. Basta depurar con algo como:
printf("%d\n", (1 << 31) < 0);
que dio salida "1" (verdadero).
Por otra parte, al hacer la máscara de bits con n = -2, que aparentemente, como vos decís, tiene representación complemento a dos,
el resultado de (n & (1 << 31)) tiene que dar esto: 1000000000000000.....
De nuevo, eso como
signed int es el número -2147483648.
Cuando lo volvés a correr hacia la derecha con >> k, ¡va agregando 1's a la izquierda!
El resultado de (n & (1 << k)) >> k, con k = 31, es 1111111111111.....
Pareciera que el corrimiento a derecha no funciona bien.
Al parecer esto no está bien definido en el estándar cuando se trata de números negativos.
Así que depende de la implementación.
Nuestro compilador lo que hace con el corrimiento a la derecha es
conservar el bit de signo.
Si el número es positivo, hace lo que esperabas que hiciera, agrega 0's a la izquierda.
Pero si es negativo, agrega 1's por la izquierda, porque en cada corrimiento de los 31 que hace, va agregando un 1 por la izquierda...
Como 111111111111.... es la representación binaria de -1, eso explica el -1 al principio de la salida de tu programa.
Conclusión: El problema no es la representación binaria del -2, sino cómo se implementa en forma local (en el compilador) la operación de corrimiento a derecha.
Buscando seguro se puede encontrar datos sobre esto.
Por ahora sólo encontré algo de C++, que igual se aplica a nosotros, seguramente:
>> Corrimiento a derecha:
El patrón de bits de expr-desplazada sufre un desplazamiento derecho del valor indicado por la expr-desplazamiento. Como en el caso anterior, ambos operandos deben ser números enteros o enumeraciones. En caso contrario, el compilador realiza una conversión automática de tipo. El resultado es del tipo del primer operando.
Una vez promovida a entero, expr-desplazamiento debe ser un entero positivo y menor que la longitud del primer operando. En caso contrario, el resultado es indefinido (depende de la implementación).
Nota: en C++Builder y GNU-C++, el signo se mantiene, lo que significa que el desplazamiento se realiza contando con el signo, el nuevo bit más significativo será 0 si se trata de un número positivo y 1 si el número es negativo ( 2.2.4a).
Fuente:
http://www.zator.com/Cpp/E4_9_3.htm