El bendito stdin...

Iniciado por zShackra, 24 Noviembre 2014, 00:28 AM

0 Miembros y 1 Visitante están viendo este tema.

zShackra

Hace unos días publiqué un tema donde al parecer se limpiaba el estándar de entrada de forma correcta y así evitábamos comportamientos indeseados en una lectura desde este, de forma portable (independiente del sistema operativo).

Hasta ahora no he tenido problemas al leer desde este con cualquiera de las siguientes funciones (aunque prefiero la segunda):

size_t leerLinea(char *arr, size_t tam)
{
int c = EOF;
size_t i;

if (!tam)
return 0;

for (i = 0; i < tam - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
arr[i] = c;

arr[i] = '\0';

while (c != '\n' && c != EOF)
c = getchar();

return i;
}

size_t leerLinea(char *arr, size_t tam)
{
int c = (tam == 1) ? EOF : 0;

if (fgets(arr, tam, stdin) == NULL)
return 0;

while (arr[strlen(arr) - 1] != '\n' && c != '\n' && c != EOF)
c = getchar();

if (arr[strlen(arr) - 1] == '\n')
arr[strlen(arr) - 1] = '\0';

return strlen(arr);
}


Ambas leen tam - 1 caracteres y descartan todo lo que haya luego de los mismos, más sin embargo, aún tengo una duda... la cual creo que haría inútil las funciones anteriores.

Según he leído en las redes, stdin es un (FILE *) stream que, al parecer, sólo se encuentra en memoria; existe un módulo kernel que lo controla y le da un espacio en esta (independientemente del sistema operativo) para que allí, se almacene la información ingresada detrás de cada salto de línea ('\n').

Bien, si todo esto es cierto, me pregunto... ¿cómo acceder a esa zona de memoria? es decir, cómo obtengo esa dirección... la requiero para jugar un rato con la stdin, y ver si me puedo deshacer de las funciones anteriores, pues si logro acceder a esa zona de memoria, tras cada lectura de entrada (con un simple fgets() sin temor a dejar basura) puedo obtener lo que deseo; lo que no, simplemente lo re-escribo con ceros y así "limpio" de una forma más directa la entrada, o bien... almaceno todo lo que el usuario ha ingresado por la terminal y luego doy formato (mi real propósito)...

Lo que busco es, en síntesis, crear una función que reciba todo lo que se ha ingresado por la terminal sin desechar nada, y para eso... requiero saber la ubicación del búfer de stdin para así, como comenté anteriormente, almacenar todo lo que se ingrese... lo deseo hacer por ocio más que nada, sé que sí es posible, ya que la plataforma .NET lo hace... si declaramos un string, no es necesario definir su tamaño ni tampoco el tamaño de la cadena a leer, la plataforma .NET re-asigna memoria hasta lograr almacenar lo que el usuario ha ingresado, en caso de no haber memoria suficiente, lanza una excepción...

Bien, es eso a lo que quisiera llegar... porque sinceramente da pena el manejo de cadenas en C (aunque en C++ se ha mejorado bastante, no es tan eficiente como la plataforma .NET)...

Ya sé que no tiene mucho sentido hacer algo semejante, pues si al caso vamos, si no estamos escribiendo un editor de textos ni nada por el estilo, no es necesario un manejo dinámico de cadenas en C, pues simplemente se usa una de las funciones anteriores para obtener lo que el programa requiere, no lo que el usuario demande... pero mi curiosidad es más grande y como .NET parte de C++ (para C#), y C++ de C, supongo es lógicamente posible. (No quiero reinventar la rueda, simplemente quiero curiosear).

Cualquier información/sugerencia/crítica será bien recibida.

EDITO:

Tal parece que... leyendo un poco sobre punteros (aún no llego a dicho capítulo en el libro de K&R), logré escribir la siguiente función, la cual lee toda la entrada de teclado hasta el salto de línea, y reasigna memoria según necesite cada vez que se llenen 4kB de esta (tengo entendido que Windows y Linux dividen la memoria virtual en páginas de 4kB), es lo que estaba buscando... así pues, me evito problemas con dejar basura en la entrada y obtengo todo lo que el usuario ha ingresado, ya luego doy formato a esa entrada...

char* leerLinea()
{
int c;
size_t p4kB = 4096, i = 0;
void *newPtr = NULL;
char *ptrString = malloc(p4kB * sizeof (char));

while (ptrString != NULL && (c = getchar()) != '\n' && c != EOF)
{
if (i == p4kB * sizeof (char))
{
p4kB += 4096;
if ((newPtr = realloc(ptrString, p4kB * sizeof (char))) != NULL)
ptrString = (char*) newPtr;
else
{
free(ptrString);
return NULL;
}
}
ptrString[i++] = c;
}

if (ptrString != NULL)
{
ptrString[i] = '\0';
ptrString = realloc(ptrString, strlen(ptrString) + 1);
}
else return NULL;

return ptrString;
}


Como se puede observar, la función devuelve un puntero donde se ubica la lectura de la entrada del usuario hasta encontrar EOF o un salto de línea, el cual es reemplazado con un carácter nulo (fin de cadena). En caso de no poder reasignar memoria, libera la utilizada hasta el momento y retorna un puntero nulo. Finalmente si logra leer todos los caracteres y sobra memoria de la reservada, reasigna memoria para liberar todo el espacio sobrante.

Agradecería usuarios más experimentados me dieran sugerencias para modificar el código, no veo más formas de optimizarlo ni hacerlo más seguro... Ya comprobé que sí es POSIX. Y a mis exigencias, cumple con lo que necesito... espero que a alguien le sirva, pero si aún hay modificaciones que se puedan aplicar para mejorar el código, no duden en hacérmelas saber.


zShackra

#2
Olvidé mencionar que estoy buscando información para un código compatible con POSIX... no gusto de enfocarme en un sólo sistema.

P.D.: Ya que logré perfeccionar la última función (char *leerLinea()) y cumple mis exigencias, y como nadie pudo aportar algo... agradezco cierren el tema, si desean.

rir3760

Algunos comentarios:

* No es necesario inicializar la variable "newPtr" a NULL ya que en su primer uso almacenas el valor de retorno de realloc.
* No es necesaria la conversión explicita en la sentencia:
ptrString = (char *) newPtr;
* El valor de sizeof(char) siempre es igual a uno, no es necesario indicarlo.
* En la comparación dentro del bucle debes utilizar "i == p4kB - 1", esto para reservar el ultimo elemento del bloque para el '\0'.
* La única forma en que la variable "ptrString" puede almacenar NULL es si la primera reserva de memoria falla (ya que las siguientes con realloc las manejas dentro del bucle), por ende si colocas el bucle dentro de una sentencia condicional if te ahorras varias comprobaciones.
* No es necesario utilizar strlen para conocer la longitud de la cadena ya que ese valor ya lo tienes almacenado en la variable "i".

Con los cambios:
char *leerLinea(void)
{
   int c;
   size_t p4kB = 4096, i = 0;
   void *newPtr;
   char *ptrString;
   
   if ((ptrString = malloc(p4kB)) != NULL){
      while ((c = getchar()) != '\n' && c != EOF){
         if (i == p4kB - 1){
            p4kB += 4096;
           
            if ((newPtr = realloc(ptrString, p4kB)) != NULL)
               ptrString = newPtr;
            else {
               free(ptrString);
               return NULL;
            }
         }
         
         ptrString[i++] = c;
      }
     
      ptrString[i++] = '\0';
      ptrString = realloc(ptrString, i);
   }
   
   return ptrString;
}


Un saludo
C retains the basic philosophy that programmers know what they are doing; it only requires that they state their intentions explicitly.
--
Kernighan & Ritchie, The C programming language

zShackra

#4
Cita de: rir3760 en 26 Noviembre 2014, 05:16 AM* No es necesario inicializar la variable "newPtr" a NULL ya que en su primer uso almacenas el valor de retorno de realloc.

Lo hacía bajo prácticas ortodoxas... prefiero que se inicialice como NULL a cualquier otra cosa, sin embargo al final es lo mismo, no hay riesgo de acceder a zonas indebidas.

Cita de: rir3760 en 26 Noviembre 2014, 05:16 AM* No es necesaria la conversión explicita en la sentencia

Así es, se me olvidó que era redundante en C, por eso no hice conversiones en las demás asignaciones.

Cita de: rir3760 en 26 Noviembre 2014, 05:16 AM* El valor de sizeof(char) siempre es igual a uno, no es necesario indicarlo.

Sí, tienes razón; me he confundido con comentarios de otros usuarios en la red...

Cita de: rir3760 en 26 Noviembre 2014, 05:16 AM* En la comparación dentro del bucle debes utilizar "i == p4kB - 1", esto para reservar el ultimo elemento del bloque para el '\0'.

Ese tipo de fallas son las que precisamente buscaba.

Así pues, tomando algunas de tus correcciones la función luce de la siguiente manera:

char* leerLinea(void)
{
int c;
size_t p4kB = 4096, i = 0;
void *newPtr = NULL;
char *ptrString = NULL;

if ((ptrString = malloc(p4kB)) != NULL)
{
while ((c = getchar()) != '\n' && c != EOF)
{
if (i + 1 == p4kB)
{
if ((newPtr = realloc(ptrString, (p4kB += 4096))) != NULL)
ptrString = newPtr;
else
{
free(ptrString);
return NULL;
}
}

ptrString[i++] = c;
}

*((ptrString = realloc(ptrString, i + 1)) + i) = '\0';
}

return ptrString;
}


Gracias por tu colaboración, cualquier otro detalle, será bien recibido.