Archivo de la categoría: C

Apuntadores en C. Recorrer variable int como si fuera un arreglo char

El capítulo 5, del libro “El Lenguaje de programación C” de K&R dice en la página 103:

“Una situación común es que cualquier byte puede ser un char, un par de celdas de un byte pueden tratarse como un entero short, y cuatro bytes adyacentes forman un long.”

Partiendo de esta idea, podemos considerar que si una variable es de tipo int, su tamaño aproximado será de 4 bytes (32 bits). En C los tipos de datos nos ayudan a darle interpretación a los bits (trata estos bits como int, char, float, double, etc).

Una variable de tipo int, de 4 bytes de largo (tamaño variable dependiendo de la implantación) se puede representar por 32 bits:

00000000 00000000 00000000 00000000

Entonces, si de alguna manera pudieramos recorrer esos bits byte por byte e interpretarlos como tipo char, podríamos almacenar letras en ese entero y recorrerlo como un arreglo.

Una variable de tipo char se vería como 8 bits:

0000000

Si recordamos, el código ASCII es una tabla en la cual podemos representar letras, es un estándar. En ASCII necesitamos 8 bits, de los cuales por lo general solo se usan 7 para los caracteres imprimibles.

En ASCII, los valores para las letras empiezan a partir del byte con valor decimal 65 (en Hex equivale a 0x41):

01000001

Entonces, que tal si en nuestra variable int, almacenamos un número como esto:

DECIMAL: 1094861636

HEX: 0x41424344

BINARIO: 01000001 01000010 01000011 01000100

ASCII: A B C D

Teniendo un numero binario de esa forma, podríamos recorrerlo con un apuntador de tipo char, que recorra byte por byte e imprima en pantalla la letra respectiva … después de todo, son solo bits, lo importante es como los interpretamos.

El código quedaría como:

#include <stdio.h>

int main(void)
{
    int c = 0;
    c = 0x41424344;
    printf("%d\n", c);
    char *d = (char *) &c;
    
    printf("%c\n", *(d));
    printf("%c\n", *(d + 1));
    printf("%c\n", *(d + 2));
    printf("%c\n\n", *(d + 3));
    
    printf("%c\n", d[0]);
    printf("%c\n", d[1]);
    printf("%c\n", d[2]);
    printf("%c\n", d[3]);
    
    return 0;
}

 

Anuncios

Hablando de C: Números Binarios

La ciencia de la computación, trata sobre “datos”, como almacenarlos, interpretarlos y operar con ellos. Cuando trabajamos en C, podemos profundizar a varios niveles.

En C, hay 4 tipos de datos básicos:

  • char: De 1 byte, la palabra char viene de character (8 bits)
  • int:  Por lo general es del tamaño natural  de la maquina, nos sirve para trabajar con enteros (4 bytes)
  • float: Utilizamos los numeros de punto flotante float de precisión sencilla para numeros que requieren parte fraccionaria (4 bytes, precisión hasta 8 digitos)
  • double: Numeros de punto flotante con doble precisión (15 a 17 digitos de precisión)

Si vamos a manejar números muy grandes o muy pequeños, es mejor utilizar double, por su ventaja en precisión sobre float. Además, hay un problema inherente a todas las computadoras …

Para una computadora es difícil representar algunos números con fracciones … es un problema que tienen todas las computadoras por diseño al tratar con números de punto flotante, por lo que si por decir, si lo que queremos es un float con valor a .001, nuestra computadora nos regresa una aproximación, como podemos ver en la imágen:

signos3

Más información sobre los problemas con aritmética de punto flotante: http://docs.python.org.ar/tutorial/2/floatingpoint.html

Teniendo esto en cuenta, un programador debería tener en cuenta considerar aproximaciones y redondeos para evitar este problema. (Si sumas .01 en un ciclo, 10 iteraciones, al final tu computadora no almacena 1, si no una aproximación).

Pero volviendo al tema inicial, para una computadora, el valor de los 0’s y 1’s que tiene almacenados, depende de la interpretación, por si mismos no significan nada.

Ejemplo,

Una variable de tipo char son 8 bits: 00000000

Aunque char significa character, char no es sino un entero pequeño, lo importante es como interpretarlo.

Si de estos 8 bits tomamos el último bit para el signo, entonces solo quedan 7 bits para representar un entero: 00000000

Bajo estos términos, qué valor tendría el número? 10000000

Depende, el número binario 10000000 representa el 128 positivo, pero para representar en binario números negativos usamos el complemento a uno y complemento a dos.

En complemento a uno, negamos los bits del número binario original, esto es, 10000000 se vuelve 01111111. Una curiosidad del complemento a uno es que hay dos formas de representar el cero. En una máquina es más común encontrar que utilice complemento a dos para representar negativos. El complemento a dos solo suma 1 al complemento a uno, esto es:

01111111 + 00000001 = 10000000

Entonces, 128 y -128 se representa igual en binario, pero depende de que consideremos o no el último bit como signo, en la imágen pongo la salida de dos programas en C donde se puede ver mejor esto:

signos4

El código para el primer programa lo pueden descargar de aqui: http://www.mediafire.com/view/z9ewo988uj60ccy/signos.c

En este programa, aunque es sencillo ponemos varios detalles, por decir, en el código estamos aplicamos una máscara a una variable char (la máscara es 0x80) con signo y sin él. También ponemos algunos ejemplos del problema de precisión y el de representación de números fraccionarios.

Creo que es un tema importante por que nos ayuda a entender otras cuestione relevantes, como el casteo de variables.

También nos ayuda a comprender mejor el código ASCII, en el cual se toma el bit de signo para control de transmisión y los caracteres imprimibles solo llegan hasta el 127. https://es.wikipedia.org/wiki/ASCII

Agrego un programa más, para los que quieran experimentar un poco más con los números binarios y sus representaciones, el programa se llama binconv, la siguiente es una captura del mismo corriendo:

signos2

El código lo pueden bajar aquí: http://www.mediafire.com/view/w3ob5ncbswqrgga/binconv.c

Con binconv pueden convertir de binario a decimal, de decimal a binario y con la opción -z imprimir de 0 a N números en su notación decimal, binaria, complemento a uno, a dos, valor hexadecimal y Octal.

Me animé a hacer los programas por que desde hacia tiempo quería investigar un poco más sobre las conversiones decimal a binario y visceversa, comparto el código por que tal vez a alguien más le sirve…

El código de binconv trae varias funciones interesantes y aprovecha el concepto de que un char, es un entero pequeño.

Igual si encuentran algo que no funcione bien agradecería la retroalimentación!

Nota Extra:

Para convertir un entero positivo a una cadena de texto binaria, podemos hacerlo de dos maneras, una es como está en el código de binconv.c (aplicando al entero % 2), pero también lo podemos hacer realizando un corrimiento a la derecha del número y aplicando un AND de bits con máscara 0x1 (o 01 en octal), con lo cual nos regresaría solo el primer bit (contando de derecha a izquierda) … finalmente invertimos la cadena y obtenemos la representación binaria, el código sería algo como lo siguiente:

void dectobin(unsigned n, char *s, int lim)
{
    char *scopy = s;
    
    while (n > 0 && (s - scopy) < lim - 1)
        *s++ = (n & 0x1) + '0', n = n >> 1;

    *s = '\0';
    // Invertimos cadena
    //reverse(scopy);
}

————–

EOF

 

Hablando de C: Programa que borra comentarios del código

Estoy probando Windows 10 y quería compilar un programa en C. En un sistema Unix o GNU/Linux, bastaría con recurrir a gcc desde la línea de comandos. Para Windows, podemos utilizar las herramientas de MinGW “Minimalist GNU for Windows”, lo podemos descargar de la página oficial del proyecto: http://www.mingw.org/

Al finalizar de instalar hay que recordar agregar al PATH de las variables de entorno la ubicación de los binarios de MinGW.

Para las variables de entorno, en Windows 10 podemos apretar CTRL+X e ir al menú System, de ahi seleccionar Advanced System Settings:

pathenv

Finalmente agregamos/verificamos la ruta hacia la carpeta bin de MinGW:

pathenv2Una vez instalado reiniciamos y ya está listo para usarse desde la línea de comandos funcionando de la misma manera que si estuvieramos trabajando desde una terminal tipo Unix o GNU/Linux, por decir para compilar un archivo de C:

> gcc nombre_archivo.c -o nombre_programa

consola

Cuando estamos aprendiendo un nuevo lenguaje de programación, los aspectos técnicos antes de empezar son algo enfadosos, pero creo que C es muy agradable en ese aspecto, más si tenemos un sistema tipo Unix, puesto que por lo general ya incluyen compiladores para C (como gcc).

¿Qué otras ventajas tiene C?, bueno, en teoría si programas en ANSI C (el estándar), tu programa debe correr bien en cualquier implantación que respete el estándar, o en otras palabras, es portable.

Estaba leyendo el libro de “El Lenguaje de Programación C” de K&R y me detuve a revisar el problema 1-23, el cual dice:

Escriba un programa para eliminar todos los comentarios de un programa en C. No olvide manejar apropiadamente las cadenas entre comillas y las constantes de carácter. Los comentarios de C no se anidan.

Bueno, creo que los que han leído el libro notaron que desde el capítulo 1 hay problemas muy interesantes, comparto el código que hice para la respuesta a este problema, a lo mejor a alguien le puede resultar útil o incluso sería agradable ver alguna respuesta alternativa.

El código lo pueden descargar del siguiente enlace: http : //www. mediafire .com/view/vxibrpd1w2zb8dk/clean_comments.c (dejo el código con fin de referencia nadamas)

Código corregido:

Nuevo enlace: http://www.mediafire.com/view/hzn69fgxp9mlz0z/clean_commentsv2.c

Tuve que modificar un poco el código para que cumpliera el propósito, considerando:

  • Saltar cadenas de caracteres
  • No imprimir in-line comments (del tipo que empiezan con //)
  • Considerar comentario las lineas de texto que empiezan con /* y terminan con */

En el capítulo 1 del libro, hablan de la función getchar(), la cual lee el siguiente caracter que recibe de la Entrada Estándar. Así por decir, podríamos desde la consola redireccionar que la Entrada la lea de un archivo y mande la Salida a otro archivo, los pasos los podemos ver en la imágen, desde la compilación hasta la ejecución de este programa teniendo como entrada el mismo programa fuente y generando un archivo test.c con el código sin comentarios:

consola2

Finalmente, comparando los dos archivos, parece que el programa cumple el cometido:

comparativa

En programación hay más de una manera de resolver el mismo problema, por lo general se busca la solución más eficiente. A veces la eficiencia se mide en líneas de código, pero más importante aún, medirlo en el tiempo de procesamiento o los pasos (notación de la Big O) que le toma a mi programa ejecutar su tarea en el mejor y en el peor de los casos.

De C, podemos hablar mucho … en el capítulo uno se tocan los temas de variables, funciones y arreglos, cuando vas en capítulos más avanzados te invitan a regresar y resolver los problemas pero ahora con apuntadores.

Uno puede tratar un arreglo como si fuera un apuntador, cuando un arreglo se pasa como argumento a una función, lo que estamos pasando en realidad es la posición en memoria del primer elemento del arreglo, dicho de otra forma, un apuntador, por lo que la función puede modificar por referencia el valor del arreglo.

Espero el post anime a más gente a voltear a la programación en C, el lenguaje para programar Sistemas Operativos y del cual la mayoría de lenguajes modernos han heredado muchos conceptos (la sintaxis en Python, PHP, Java y Javascript a mi gusto es muy parecida).

EOF