Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


5.5.2a  Problemas y precauciones con Directorios y Ficheros

§1 Sinopsis

En lo que se refiere al manejo de ficheros y directorios en la librería clásica C++ (heredada de C), habría que señalar dos dificultades principales: (1) La escasez de funciones proporcionadas por la Librería Estándar. Por ejemplo, es imposible calcular el tamaño y atributos de un fichero utilizando exclusivamente las funciones estándar. (2) La escasa o nula información que proporcionan en caso de error. Por ejemplo, la función fwrite, devuelve el número de ítems leídos, y en caso de error un número menor, pero sin indicación del tipo de error o del número real de bytes leídos. Por su parte rewind no devuelve señal alguna del resultado de la operación [1].

Nota: por supuesto, las operaciones de red pertenecen a una galaxia ajena al lenguaje. En caso de necesitarlas hay que acudir a llamadas al Sistema o a librerías de terceros (esperemos que en un futuro no lejano sean incorporadas al Estándar algunas utilidades con este propósito).


Las funciones complementarias proporcionadas por las plataformas de desarrollo son en consecuencia muy abundantes, pero con el inconveniente de su falta de portabilidad. A título orientativo, la selección de funciones de la página anterior, que recoge prácticamente todas las utilidades relacionadas con el manejo de ficheros y directorios de los compiladores C++ Borland y MS, contiene 86 funciones, de las cuales solo 25 pertenecen al C++ Estándar.

§2 Lecturas/escrituras binarias y de texto

Como comentábamos en la página anterior, las operaciones de lectura/escrituras relacionadas con ficheros abiertos en modo texto, implican ciertas conversiones de código; de forma que los ficheros origen y destino no son iguales. Por ejemplo, la imagen guardada en el disco y la obtenida en memoria después de una operación de lectura. Además la respuesta proporcionada por la función fread no tiene en cuenta la forma de lectura utilizada, lo que puede dar lugar a situaciones desconcertantes.

Ejemplo:

Supongamos que necesitamos leer el contenido de un fichero fichero.nnn en memoria.  El primer problema es calcular el tamaño a leer, para lo que debemos utilizar una función no estándar (observe las secuencias de escape \\ utilizadas en la definición del fichero).

Fase-1 Apertura:

FILE* handle = fopen("D:\\path-name\\fichero.nnn", "r+b");

if (handle == NULL)

    std::cout << "Error de apertura!!" << std::endl;

long size = filelength(_fileno(handle));

El uso de funciones estándar para este propósito obliga a un método indirecto utilizando dos funciones:

int res = fseek(handle, 0, SEEK_END);   // ir al final del fichero
long int pos = ftell(handle);           // ver que posición es esta
cout << "Longitud del fichero: " << pos << " Bytes" << endl;


Fase-2 Lectura: a continuación puede procederse a la lectura propiamente dicha.

char buffer[MAXSIZE];       // buffer de lectura

size_t result = fread(buffer, 1, size, handle);

if (result != size)

    std::cout << "Error de lectura!!" << std::endl;

else

    std::cout << "Ok. Leidos " << size << " bytes" << std::endl;


Observe que MAXSIZE es una constante cuyo valor debe ser suficiente para albergar el máximo tamaño de fichero previsto.  Recuerde que el tamaño de la matriz debe ser un valor resuelto en tiempo de compilación ( 4.3.1). Por consiguiente no puede utilizarse una expresión del tipo

char buffer[size];       // Error!!

Recuerde también que si se realizan otras operaciones con el fichero después de la 1ª fase, es necesario volver a reposicionar el fp (file-pointer) al comienzo antes de proceder a la 2ª:

fseek(handle, 0L, SEEK_SET);


Suponiendo que el fichero ha sido felizmente abierto, el resultado es satisfactorio, por lo que decidimos utilizar la rutina de forma más o menos "oficial" en nuestras operaciones de lectura. Sin embargo, días más tarde, la rutina muestra errores persistentes sin que sea posible determinar la causa. Después de unas horas de pruebas infructuosas (probando todo lo demás, ya que estamos seguros que la rutina SI funciona), descubrimos que es un fichero abierto en modo texto. En este caso, aunque el tamaño a leer está correctamente indicado y el fichero correctamente abierto, descubrimos que el conteo de caracteres leídos se realiza después de la conversión, con lo que si el fichero tiene más de una línea de texto, es decir, si tiene al menos un CR-LF, el tamaño leído no coincide con el valor size, y la rutina devuelve error. Para mayor consternación, a pesar del error, el buffer contiene efectivamente el texto leído, pero cerca del final el contenido es incorrecto. Este trozo tanto mayor cuanto mayor sea el fichero leído (más líneas contenga [2] ).

En estos casos (lecturas en modo texto), la única forma de leer correctamente el contenido del fichero es utilizar algo como:

long fsize = 0;

char cread;
do {
   *(buffer+fsize) = cread = fgetc(handle);
   ++fsize;
} while ( !feof(handle) && cread != EOF );

Aunque el sistema realiza un "buffering" automático, y en realidad el bucle anterior no significa un acceso a disco por cada carácter leído, es evidente que el procedimiento estándar para leer un simple fichero es de lo más rudimentario.

Como ejemplo de lo anteriormente expuesto, se incluye un código que permite leer correctamente el contenido de un fichero de texto:

#include <iostream>
#include <stdio.h>         // para operaciones con ficheros

#define MAXSIZE 64000      // un valor suficientemente grande
char buffer[MAXSIZE];      // Buffer de lectura

FILE* handle = fopen("D:\\path-name\\fichero.sss", "rt");
if (handle == NULL) {
    std::cout << "Error de apertura fichero!!" << std::endl;
    return;
}

int res = fseek(handle, 0, SEEK_SET);  // ir al inicio del fichero
long fsize = 0;
char cread;

do {
    *(buffer + fsize) = cread = fgetc(handle);
    ++fsize;
} while ( !feof(handle) && cread != EOF );
buffer[fsize] = '\0';
--fsize;               // eliminar incremento del último bucle

std::cout << "El fichero es de " << fsize << " bytes" << std::endl;
std::cout << buffer << std::end;    // mostrar contenido

fclose(handle);    // No olvide cerrar el fichero!!
...

§3 Escritura

Igual que ocurre con las operaciones de lectura, las escrituras sucesivas se realizan secuencialmente. Cada nueva escritura se realiza a continuación de la anterior. Cuando se alcanza el final de fichero actual, cada escritura aumenta correspondientemente el tamaño del fichero resultante. Pero si se utilizan sentencias de posicionado previas (por ejemplo fseek), debe recordar que es posible iniciar una nueva escritura más allá del límite actual del fichero, y que consecuentemente, esta operación extenderá el tamaño del fichero por encima de la cantidad de caracteres realmente escritos.

Ejemplo:

FILE* handle = fopen("D:\\path-name\\fichero.nnn", "w+b");

                          // tamaño actual del fichero 0 bytes.

char buffer[] = "AEIOU";  // buffer de escritura

fwrite(buffer, strlen(buffer), 1, handle);

                          // tamaño actual del fichero 5 bytes.

fseek(handle, 100L, SEEK_SET);

fwrite(buffer, strlen(buffer), 1, handle);

                          // tamaño actual del fichero 105 bytes.


  Inicio.


[1]   Para terminar de arreglarlo, la documentación de MS Visual C++ 6.0 nos informa que la rutina fseek puede comportarse erróneamente en el final de un fichero de texto.

[2] Para dar una idea de las magnitudes involucradas en el proceso, indiquemos que un fichero de texto, guardado como .txt con el WordPad de Windows, que aparecía como un fichero de disco de 65.579 bytes, se transformó en un buffer de 64.385 bytes después de leído en modo texto.