Foros del Web » Programación para mayores de 30 ;) » Programación General »

Escribir bit a bit un archivo.

Estas en el tema de Escribir bit a bit un archivo. en el foro de Programación General en Foros del Web. Hola, hace poco que estoy yendo a clases de programación, ya sabia bastante antes(dentro de lo novato), pero queria aprender mucho mas y tuve la ...
  #1 (permalink)  
Antiguo 18/08/2003, 02:11
Usuario no validado
 
Fecha de Ingreso: junio-2003
Ubicación: Aguacate
Mensajes: 56
Antigüedad: 20 años, 10 meses
Puntos: 0
Escribir bit a bit un archivo.

Hola, hace poco que estoy yendo a clases de programación, ya sabia bastante antes(dentro de lo novato), pero queria aprender mucho mas y tuve la suerte de ke llegara un profesor de programacion a mi pueblo... bueno, pos me mando un proyecto para ir haciendo durante las clases(lo diseñaba yo, libre totalmente), decidí por hacer un juego de plataformas, y una de sus particulariades es que para no tener que "programar" los mapas, se escribirian en archivos aparte que el juego interpretaria(pero claro, no voy a gastar un byte por cada dato que quisiera escribir, seria mejor escribirlo a nivel de bits) y ademas , tambien pense en la posibilidad de que los datos del mapa quedaran comprimidos(o no, segun las conveniencias de quien lo cree), asi que la única manera que se me ocurrió fué manipulando los bits y reorganizandolos de tal manera que se suprimieran la mayor cantidad de repeticiones, pues bien:

Como puedo escribir de bit en bit en un archivo con C/C++?
  #2 (permalink)  
Antiguo 18/08/2003, 08:30
Avatar de Mithrandir
Colaborador
 
Fecha de Ingreso: abril-2003
Mensajes: 12.106
Antigüedad: 21 años
Puntos: 25
Hasta donde se no se puede solo a nivel de bytes.

Lo que debes intentar es usar operaciones logicas para accesar a cada bit de cada byte que vayas a escribir.

Lo que lograrias es controlar cada bit, pero escribiendo a nivel de bytes.

Usa las operaciones a nivel de bit (BITWISE OPERATORS):
AND (&), OR (|), XOR (creo que ^) NOT (no lo recuerdo) y no recuerdo si hay mas para realizar las asignaciones y comparaciones a nivel binario y al final tengas un byte controlado.
__________________
"El hombre, en su orgullo, creó a Dios a su imagen y semejanza."
Friedrich Nietzsche
  #3 (permalink)  
Antiguo 18/08/2003, 09:04
 
Fecha de Ingreso: julio-2003
Mensajes: 165
Antigüedad: 20 años, 9 meses
Puntos: 1
Hola andreumic,

¡Qué problema más bonito éste del que hablas! Realmente me parece muy interesante, de modo que trataré de contribuir un poco de ayuda, en cuanto me sea posible.

Personalmente, este tipo de temas me entusiasman mucho, de modo que puede que me extienda un poco sobre cosas relacionadas con el tema... espero no hacer una disertación muy aburrida :). Parte de la información que voy a mencionar posiblemente ya la conozcas, pero me parece importante incluirla, aunque fuese al menos por un sentido discreto de completitud (jeje, parece que me hubieran regalado un diccionario en Navidad...).


Ciertamente es una buena idea diseñar un sistema coherente y eficaz para almacenar datos en tus aplicaciones. Bien podría decirse que en la actualidad, con el advenimiento de elementos de hardware más poderosos, sofisticados y económicos (considerablemente más económicos que hace unos años), el problema de almacenar datos, desperdiciando unos cuantos bits en el proceso, no es gran cosa. Pero, por una parte, difícilmente podría alguien objetar al diseño de soluciones más eficaces, cuando el costo adicional en términos de desarrollo se limita a un poco de planeación y sentido común.

Por otra parte, no se trata sólo del aprovechamiento del espacio en disco, también se trata de la organización. Creo que muchos programadores podrían estar de acuerdo conmigo si digo que, usualmente, cuando se trabaja con formatos de almacenamiento de datos, resulta mucho más importante (a veces en varios órdenes de magnitud) que el formato mismo esté diseñado de forma coherente y flexible, que otras cosas como su nivel de optimización orientado al tamaño de almacenamiento, o rapidez de procesamiento.

Es decir, cuando se manipula información sobre archivos mediante algún formato particular, suele resultar más importante la capacidad de este formato para describir información, que su capacidad de ahorrar espacio en disco, u otras propiedades colaterales que se concentran más en problemas específicos de implementación.

De hecho, al llegar un formato dado a cumplir con su cometido, cuando de describir información eficazmente se trata, otras propiedades secundarias como su capacidad de almacenar información en forma concisa, suelen venir por añadidura. Por ejemplo, en la actualidad es posible apreciar los efectos que conlleva el uso de un formato dado como el XML, el cual, dígase lo que se diga, es un formato increíblemente poderoso para especificar y almacenar meta-información (información sobre otra información). Concretamente, es posible apreciar en forma general los beneficios de este tipo de solución echándole un vistazo a cierto tipo de aplicaciones como, por ejemplo, los procesadores de texto.

En la actualidad existen procesadores de texto que usan como formato nativo un dialecto de XML (o SGML), y al compararlos con formatos que usan otras aplicaciones (formatos usalmente privados e intrincados), resulta evidente la superioridad en campos como el espacio de almacenamiento y rapidez de procesamiento de los formatos más flexibles y que se concentron en los datos mismos, no en cosas secundarios como la ofuscación de la información para conservar la privacidad del formato... Pero la superioridad técnica del XML no está en la disponibilidad de analizadores sintácticos (o parsers) eficaces, ya que éstas son solo propiedades emergentes. El valor real del XML está en su capacidad de describir información.


Vaya, ya llevo varios párrafos, y ni siquiera me he referido a los bits. En conclusión, para continuar con la parte divertida (el código), mi recomendación inicial es que no escatimes esfuerzos en la planeación de tus formatos de almacenamiento de información. Un poco de planeación puede representar una diferencia significativa a largo plazo, cuando tus programas sean más sofisticados y tus necesidades como desarrollador evolucionen.


Ahora sí, hablemos de bits, C, fopen(), etc. El tipo de cosas que seguramente esperabas desde un comienzo.

Antes que nada, algunos recursos y construcciones del lenguaje que me parece que son muy útiles a la hora de trabajar con bits son:

(nota: voy a referirme a C, ya que es mi lenguaje de preferencia (al menos sobre la alternativa de C++))

* Las enumeraciones. Los símbolos declarados con `enum' suelen ser extremadamente útiles. En realidad, a veces pienso que `enum' es un recurso del lenguaje altamente subestimado. Claro está que muchos programadores suelen cubrir la necesidad que `enum' está diseñada para suplir mediante macros usando la directiva `define', por ejemplo:

Código:
/* Conjunto de banderas particular creadas para manipular bits */

#define PROPIEDAD_UNO    1
#define PROPIEDAD_DOS    2
#define PROPIEDAD_TRES   4
#define PROPIEDAD_CUATRO 8
Al usar enumeraciones (tal y como el Señor manda), la solución resulta natural, y contamos con la ventaja adicional de poder crear un tipo de dato especial, en lugar de limitarnos a `int':

Código:
/* Forma bonita de definir los simbolos para el manejo de banderas. De
 * este modo, los simbolos PROPIEDAD_* estan `atados' a un tipo de
 * datos particular */

typedef enum _MiDato MiDato;

enum _MiDato
{
    PROPIEDAD_UNO    = 1,
    PROPIEDAD_DOS    = 2,
    PROPIEDAD_TRES   = 4,
    PROPIEDAD_CUATRO = 8
};

* Los campos de bits. Otro recurso, extremadamente útil en ciertos casos particulares, es la definición de campos de bits en estructuras de C:

Código:
struct paquete {
    unsigned int bandera1:1;
    unsigned int bandera2:1;

    unsigned int corto:4;

    unsigned int raro:9;
};
En el anterior ejemplo se definen dos campos de 1 bit de tamaño cada uno, un campo de 4 bits, y otro un poco curioso de 9 bits. Esta capacidad de definir explícitamente el número de bits de un campo en una estructura es muy útil cuando se trabaja sobre registros que están diseñados en una forma particular, en donde algunas campos no cuentan necesariamente con tamaños que correspondan a los tamaños de los tipos de datos nativos en C.

Lamentablemente, esta estratecia suele contar con restricciones dadas por el compilador de C que se utilice, y la arquitectura de la máquina en donde se trabaja.


* Y finalmente, por supuesto, están los operadores de bits de C: & , |, ^, ~, << y >>. En su orden, los operadores implementan las funciones: and, or, xor, complemento, translado de bits a izquierda, translado de bits a derecha.

Los operadores de bits son otro recurso sub-aprovechado con frecuencia. De hecho, no es raro ver listados de código que en ocasiones realizan operaciones complicadas e ineficientes (recurriendo incluso a veces a realizar transformaciones entre valores enteros y reales), cuando en efecto cosas como un simple translado de bits podría lograr el resultado deseado. Algunos lo mencionan como anécdotas graciosas, pero se trata de un problema real del que a veces se padece a la hora de programar.


Bien, pasemos ahora a un ejemplo concreto, en donde implementemos el tipo de cosas que incluyes en tu enunciado (particularmente, la lectura/escritura de bits en archivos). Como sabrás, el acceso a disco se realiza mediante paquetes de bytes (u octetos, como se les llama en España). Estos bytes constituyen la unidad más pequeña de operación cuando se desea "conversar" con los sistemas de archivo actuales. Esto quiere decir que a la hora de hacer manipulaciones particulares de bits, debe tenerse en cuenta este detalle: todas las operaciones, en últimas, deben expresarse en paqueticos de a ocho. Por fortuna, esto no es muy problemático. Después de todo, hemos crecido en la cultura de los bytes, ¿no es así?

Pero suficiente charla. Pasemos al ejercicio (por fin...).

(continúa)
  #4 (permalink)  
Antiguo 18/08/2003, 09:08
 
Fecha de Ingreso: julio-2003
Mensajes: 165
Antigüedad: 20 años, 9 meses
Puntos: 1
(continuación)

Código:
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>



/*** Macros ***/

#define TAMBUF  1024  /* Tamanyo para bufer de entrada */



/*** Definicion de tipos de datos personalizados ***/

/* Tipo de datos para la gestion del menu del programa */

typedef enum _OpcionMenu OpcionMenu;

enum _OpcionMenu
{
    MENU_LISTAR_DATOS = 1,

    MENU_AGREGAR_DATO,
    MENU_ELIMINAR_DATO,
    MENU_MODIFICAR_DATO,

    MENU_LEER_ARCHIVO,
    MENU_GUARDAR_ARCHIVO,

    MENU_SALIR,

    MENU_FIN  /* Elemento especial para marcar el final de la
               * enumeracion */
};


/* Tipo de datos que almacena valores y descripciones para el menu */

typedef struct _Menu Menu;

struct _Menu
{
    OpcionMenu valor;

    /* Le damos un tamanyo fijo, a sabiendas de que solo el
     * programador puede operar sobre este tipo de
     * estructuras. Asumimos que el programador sabe lo que hace,
     * cierto? */
    char descripcion[81];
};


typedef unsigned char uchar;  /* Alias para unsigned char */



/*** Prototipos de funciones ***/

void arreglar (char *);

void bit_definir   (uchar *, int, int);
int  bit_recuperar (uchar *, int);

int  bits_agregar   (uchar **, int);
int  bits_eliminar  (uchar **, int);
void bits_listar    (uchar *, int);
void bits_modificar (uchar *, int);

void bits_guardar   (uchar *, int);
int  bits_leer      (uchar **);

int leer_entero (void);



/*** Funcion principal ***/

int
main (void)
{
    static Menu datos_menu[] = {
        {MENU_LISTAR_DATOS,    "Listar datos"},
        {MENU_AGREGAR_DATO,    "Agregar dato"},
        {MENU_ELIMINAR_DATO,   "Eliminar dato"},
        {MENU_MODIFICAR_DATO,  "Modificar dato"},
        {MENU_LEER_ARCHIVO,    "Leer datos desde archivo"},
        {MENU_GUARDAR_ARCHIVO, "Guardar datos en archivo"},
        {MENU_SALIR,           "Salir"}};

    OpcionMenu opcion;
    int i;

    uchar *secuencia = NULL;  /* Secuencia de bits */
    int cantidad = 0;  /* Cantidad de bits que se estan manipulando */


    /* Mensaje de introduccion. Se siente un poco mejor cuando el
     * programa le da la bienvenida al usuario, y le dice brevemente
     * de que se trata el asunto. */

    printf ("\n"
"Bienvenido.\n"
"Este programa realiza algunas manipulaciones simples con bits.\n\n"
"Por favor seleccione una opcion:\n\n");


    for (i = 0; i < MENU_FIN - 1; i++)
        printf ("  %d. %s\n", datos_menu[i].valor, datos_menu[i].descripcion);


    do {
        printf ("\nopcion> ");

        opcion = leer_entero ();

        switch (opcion) {
        case MENU_LISTAR_DATOS:
            bits_listar (secuencia, cantidad);
            break;

        case MENU_AGREGAR_DATO:
            cantidad = bits_agregar (&secuencia, cantidad);
            break;

        case MENU_ELIMINAR_DATO:
            cantidad = bits_eliminar (&secuencia, cantidad);
            break;

        case MENU_MODIFICAR_DATO:
            bits_modificar (secuencia, cantidad);
            break;

        case MENU_LEER_ARCHIVO:
            cantidad = bits_leer (&secuencia);
            break;

        case MENU_GUARDAR_ARCHIVO:
            bits_guardar (secuencia, cantidad);
            break;

        case MENU_SALIR:
            break;

        default:
            printf ("\nOpcion invalida.\n");
        }

    } while (opcion != MENU_SALIR);

    printf ("\nAdios pues..\n");

    return 0;
}



/*** Declaracion de funciones ***/


/* arreglar()
 *
 * Sencilla funcion que se encarga de asegurarse de que una cadena
 * solo contenga caracteres validos, de acuerdo a la funcion
 * isgraph(). Todos los caracteres no-validos son eliminados */

void
arreglar (char *cadena)
{
    int i, j;
    int longitud;

    longitud = strlen (cadena);

    for (i = j = 0; i < longitud; i++) {
        if (isgraph(cadena[i])) {
            if (i != j)
                cadena[j++] = cadena[i];
            else
                j++;
        }
    }

    cadena[j] = '\0';
}


/* bit_definir()
 *
 * Define el valor de un bit determinado dentro de una secuencia de
 * bits, que se asume esta en orden little-endian. Recibe en orden: la
 * secuencia de bits, la posicion a definir, y el nuevo valor para el
 * bit */

void
bit_definir (uchar *sec, int pos, int val)
{
    uchar temp = 1 << (7 - (pos % 8));

    sec[pos / 8] = (uchar) (val ? sec[pos / 8] | ((uchar)temp) :
                            sec[pos / 8] & ((uchar)~temp));
}


/* bit_recuperar()
 *
 * Devuelve el valor de un bit determinado, en una secuencia de bytes
 * dada. Recibe la secuencia de bytes, y la posicion del bit que se
 * desea consultar. Asume que la secuencia esta en orden
 * little-endian. */

int
bit_recuperar (uchar *sec, int pos)
{
    return (int)((sec[pos / 8] >> (7 - (pos % 8))) & 1);
}


/* bits_agregar()
 *
 * Recibe una referencia a una secuencia de bits y la cantidad de bits
 * que contiene. Pide un nuevo valor al usuario y lo agrega al final
 * de la secuencia. Retorna el nuevo numero de elementos de la
 * secuencia. */

int
bits_agregar (uchar **sec, int n)
{
    int nuevo_bit;

    if (n == 0)
        *sec = (uchar *) malloc (sizeof (uchar));
    else if (n % 8 == 0)
        *sec = (uchar *) realloc (*sec, sizeof (uchar) * (n / 8 + 1));

    if (*sec == NULL) {
        printf ("Error de ubicacion de memoria.\n");
        exit (1);
    }

    printf ("\nIngrese el valor del nuevo bit:\n");

    do {
        printf ("> ");
        nuevo_bit = leer_entero ();

        if (nuevo_bit < 0 || nuevo_bit > 1)
            printf ("\nPor favor ingrese 0 o 1.\n");

    } while (nuevo_bit != 0 && nuevo_bit != 1);

    bit_definir (*sec, n, nuevo_bit);

    return n + 1;
}


/* bits_eliminar()
 *
 * Recibe una referencia a una secuencia de bits, y la cantidad de
 * bits que contiene. Esta funcion que pide al usuario una posicion
 * dentro de la secuencia y procede a eliminar el dato en tal
 * posicion. Retorna el nuevo numero de elementos de la secuencia. */

int
bits_eliminar (uchar **sec, int n)
{
    int pos;
    int i;

    if (n < 1) {
        printf ("\nNo hay elementos para eliminar.\n");
        return n;
    }

    else if (n == 1) {
        free (*sec);
        *sec = NULL;

        printf ("\nEl ultimo elemento disponible fue eliminado.\n");
        return 0;
    }

    printf ("\nIngrese la posicion a eliminar (valor entre 0 y %d)\n", n - 1);

    do {
        printf ("> ");
        pos = leer_entero ();

        if (pos < 0 || pos >= n)
            printf ("Ingrese un valor entre 0 y %d\n", n - 1);
    } while (pos < 0 || pos >= n);


    /* Aqui, por sencillez, optaremos por usar nuestras funciones
     * bit_recuperar() y bit_definir(), aunque este proceso de
     * eliminacion podria optimizarse considerablemente usando
     * translaciones directas de bits. */

    for (i = pos + 1; i < n; i++)
        bit_definir (*sec, i - 1, bit_recuperar (*sec, i));

    printf ("\nSe ha eliminado la posicion %d.\n", pos);

    return n - 1;
}


/* bits_listar()
 *
 * Funcion que lista el valor de una secuencia de bits. Recibe la
 * secuencia (en forma de apuntador a ``unsigned char'', y el numero
 * de bits a listar */

void
bits_listar (unsigned char *sec, int n)
{
    int i;

    if (n < 1) {
        printf ("\nNo hay datos en el momento.\n");
        return;
    }

    printf ("\nLa secuencia de bits actual es:\n\n");

    for (i = 0; i < n; i++)
        printf ("%d\t", bit_recuperar (sec, i));

    printf ("\n\nTotal de bits: %d\n", n);
}


/* bits_modificar() Recibe una secuencia de bits y la cantidad de
 * valores en la secuencia. Pide al usuario una posicion a modificar y
 * el nuevo valor para tal posicion, para realizar asi la
 * modificacion. */

void
bits_modificar (uchar *sec, int n)
{
    int nuevo_bit;
    int pos;

    if (n < 1) {
        printf ("\nNo hay datos para modificar.\n");
        return;
    }
    
    if (n == 1)
        pos = 0;
    else {
        printf ("\nIngrese la posicion a modificar (valor entre 0 y %d)\n",
                n - 1);

        do {
            printf ("> ");
            pos = leer_entero ();

            if (pos < 0 || pos >= n)
                printf ("Ingrese un valor entre 0 y %d\n", n - 1);
        } while (pos < 0 || pos >= n);
    }

    printf ("\nIngrese el nuevo valor del bit No. %d:\n", pos);

    do {
        printf ("> ");
        nuevo_bit = leer_entero ();

        if (nuevo_bit < 0 || nuevo_bit > 1)
            printf ("\nPor favor ingrese 0 o 1.\n");

    } while (nuevo_bit != 0 && nuevo_bit != 1);

    bit_definir (sec, pos, nuevo_bit);
}


/* leer_entero()
 *
 * Funcion que se limita a leer un valor entero desde el teclado */

int
leer_entero (void)
{
    char bufer[TAMBUF];

    fgets (bufer, TAMBUF, stdin);

    return atoi (bufer);
}
(continúa)
  #5 (permalink)  
Antiguo 18/08/2003, 09:09
 
Fecha de Ingreso: julio-2003
Mensajes: 165
Antigüedad: 20 años, 9 meses
Puntos: 1
(continuación)

Código:
/* bits_guardar()
 *
 * Guardar una secuencia de bits en un archivo de disco especificado
 * por el usuario. */

void
bits_guardar (uchar *sec, int n)
{
    FILE *archivo;

    char nombre[TAMBUF];  /* Nombre de archivo */
    int i;

    if (n < 1) {
        printf ("\nNo hay datos para almacenar.\n");
        return;
    }

    printf ("\nIndique el nombre del archivo para almacenar los datos:\n> ");

    fgets (nombre, TAMBUF, stdin);
    arreglar (nombre);

    archivo = fopen (nombre, "wb");

    if (archivo == NULL) {
        printf ("\nNo ha podido abrirse el archivo `%s' para escritura.\n",
                nombre);

        return;
    }

    for (i = 0; i < (n - 1) / 8 + 1; i++)
        fprintf (archivo, "%c", sec[i]);

    if (fclose (archivo) != 0) {
        printf ("\nHa ocurrido un error al cerrar el archivo.\n");
        return;
    }

    printf ("\nSe escribieron %d bits en el archivo.\n", n);
}


/* bits_leer()
 *
 * Funcion que lee desde un archivo especificado por el usuario, un
 * numero de bits dado. Recibe una referencia a la secuencia en donde
 * deben almacenarse los datos, y se devuelve el numero de bits leidos
 * desde el archivo. */

int
bits_leer (uchar **sec)
{
    FILE *archivo;

    char nombre[TAMBUF];  /* Nombre de archivo */
    int i, n;

    printf ("\nIndique el nombre del archivo desde donde leer los datos:\n> ");

    fgets (nombre, TAMBUF, stdin);
    arreglar (nombre);

    printf ("\nIndique el numero de bits a leer:\n> ");
    n = leer_entero ();

    if (n < 1) {
        printf ("No se va a leer menos de 1 bit desde el archivo.\n");
        return 0;
    }

    if (*sec == NULL)
        *sec = (uchar *) malloc (sizeof (uchar) * ((n - 1) / 8 + 1));
    else
        *sec = (uchar *) realloc (*sec, sizeof (uchar) * ((n - 1) / 8 + 1));


    if (*sec == NULL) {
        printf ("Error al asignar memoria.\n");
        exit (1);
    }

    archivo = fopen (nombre, "rb");

    if (archivo == NULL) {
        printf ("\nNo ha podido abrirse el archivo `%s' para lectura.\n",
                nombre);

        exit (1);
    }

    for (i = 0; i < (n - 1) / 8 + 1; i++)
        fscanf (archivo, "%c", &(*sec[i]));

    if (fclose (archivo) != 0) {
        printf ("\nHa ocurrido un error al cerrar el archivo.\n");
        exit (1);
    }

    printf ("\nSe han leido %d bits desde el archivo.\n", n);

    return n;
}
Con este pequeño programa pueden manipularse un número arbitrario de bits (en realidad, una cantidad de bits únicamente restringida por la memoria de la máquina), incluyendo la lectura/escritura de bits desde/hacia archivos.

En realidad no lo he depurado exhaustivamente, pero es posible que te resulte útil de alguna forma. Y pues cualquier duda que te nazca, bien puedes formular tu consulta. De hecho siento que hay temas que aun cabría mencionar en esta discusión, como los formatos de archivos que trabajan en base a `tablas' de registros, de las orientaciones big-endian y little-endian, de los juegos de caracteres, en fin...

Vaya que si me ha entusiasmado esta cuestión, ¿eh? :) En todo caso, aquí estamos para colaborar, y esto de la programación es tan bonito... Es apenas natural entusiasmarse, sólo espero que no les haya aburrido...

Un cordial saludo
  #6 (permalink)  
Antiguo 19/08/2003, 01:36
Usuario no validado
 
Fecha de Ingreso: junio-2003
Ubicación: Aguacate
Mensajes: 56
Antigüedad: 20 años, 10 meses
Puntos: 0
Gracias

Weno, te agradezco toda esta informacion, todavia no la e leido del todo exhaustivamente, pero ahora lo hare para entenderlo todo y poder aprovechar tus conocimientos... jeje, de verdad que te lo agradezco muchisimo, adios.
Atención: Estás leyendo un tema que no tiene actividad desde hace más de 6 MESES, te recomendamos abrir un Nuevo tema en lugar de responder al actual.
Respuesta




La zona horaria es GMT -6. Ahora son las 04:23.