2011-07-11 14 views
18

Así que estaba pasando por K & R segunda edición haciendo los ejercicios. Sintiéndome bastante seguro después de hacer algunos ejercicios, pensé que verificaría las implementaciones reales de estas funciones. Fue entonces cuando mi confianza huyó de la escena. No pude entender nada de eso.Comprensión de las implementaciones de función de biblioteca C integradas

Por ejemplo puedo comprobar el getchar():

Aquí es el prototipo en libio/stdio.h

extern int getchar (void); 

Así lo sigo a través de él y obtiene la siguiente:

__STDIO_INLINE int 
getchar (void) 
{ 
    return _IO_getc (stdin); 
} 

Una vez más lo sigo a el libio/getc.c:

int 
_IO_getc (fp) 
    FILE *fp; 
{ 
    int result; 
    CHECK_FILE (fp, EOF); 
    _IO_acquire_lock (fp); 
    result = _IO_getc_unlocked (fp); 
    _IO_release_lock (fp); 
    return result; 
} 

Y me llevan a otro archivo de cabecera libio/libio.h, que es bastante críptica:

#define _IO_getc_unlocked(_fp) \ 
     (_IO_BE ((_fp)->_IO_read_ptr >= (_fp)->_IO_read_end, 0) \ 
    ? __uflow (_fp) : *(unsigned char *) (_fp)->_IO_read_ptr++) 

que es donde finalmente terminé mi viaje.

Mi pregunta es bastante amplia. ¿Qué significa todo esto? Por el resto de mi vida, no podría deducir nada lógico mirando el código. Parece un grupo de códigos abstraídos capas tras capa.

Más importante aún, cuando es lo que realmente obtener el carácter de stdin

+3

se lee el carácter cuando se llama '__uflow()'. –

Respuesta

24

_IO_getc_unlocked es una macro inlinable. La idea es que puedes obtener un personaje de la transmisión sin tener que invocar una función, lo que hace que sea lo suficientemente rápido como para usarlo en bucles ajustados, etc.

Vamos a desmontar una capa a la vez. Primero, ¿qué es _IO_BE?

/usr/include/libio.h:# define _IO_BE(expr, res) __builtin_expect ((expr), res) 

_IO_BE es una sugerencia para el compilador, que se expr generalmente se evalúan como res. Se usa para estructurar el flujo de código para que sea más rápido cuando la expectativa es verdadera, pero no tiene otro efecto semántico. Así podemos deshacernos de eso, lo que nos deja:

#define _IO_getc_unlocked(_fp) \ 
    (((_fp)->_IO_read_ptr >= (_fp)->_IO_read_end) \ 
    ? __uflow(_fp) : *(unsigned char *)(_fp)->_IO_read_ptr++)) 

Vamos a convertir esto en una función en línea para mayor claridad:

inline int _IO_getc_unlocked(FILE *fp) { 
    if (_fp->_IO_read_ptr >= _fp->_IO_read_end) 
    return __uflow(_fp); 
    else 
    return *(unsigned char *)(_fp->_IO_read_ptr++); 
} 

En resumen, tenemos un puntero a un búfer, y un puntero hasta el final del búfer. Verificamos si el puntero está fuera del buffer; de lo contrario, lo incrementamos y devolvemos el carácter que tenía al valor anterior. De lo contrario, llamamos al __uflow para volver a llenar el búfer y devolver el carácter recién leído.

Como tal, esto nos permite evitar la sobrecarga de una llamada de función hasta que realmente tengamos que hacer IO para rellenar el búfer de entrada.

Tenga en cuenta que las funciones de biblioteca estándar pueden ser complicadas de esta manera; también pueden usar extensiones para el lenguaje C (como __builtin_expect) que NO son estándar y pueden NO funcionar en todos los compiladores. Lo hacen porque necesitan ser rápidos y porque pueden hacer suposiciones sobre qué compilador están usando. En términos generales, su propio código no debe usar dichas extensiones a menos que sea absolutamente necesario, ya que dificultará la migración a otras plataformas.

+0

¿Estás seguro de que 'char sin signo *' es correcto como el tipo de devolución? ¿No debería ser 'int'? –

+0

Ah, podría ser. No me molesté en buscar qué tipo de devolución de '__uflow' era. Pero de todos modos, es solo un ejemplo. – bdonlan

+0

Gracias, muy útil. Solo tengo una pregunta más.¿Cómo es el 'char * _IO_read_ptr;' 'char * _IO_read_end;' conjunto? – saint

1

La razón hay una biblioteca estándar es que no debería ser necesario conocer los detalles exactos de implantación de estas funciones. El código que implementa las llamadas a la biblioteca en algún momento tiene que usar llamadas al sistema no estándar que tienen que tratar con problemas que pueden no preocuparle. Si está aprendiendo C, asegúrese de que puede comprender otros programas C además de stdlib una vez que vea un poco más el archivo stdlib, pero aún no tendrá mucho sentido hasta que comprenda las llamadas al sistema involucradas.

0

La definición de getchar() redefine la solicitud como una solicitud específica para un carácter de stdin.

La definición de _IO_getc() hace una comprobación de cordura para asegurarse de que el ARCHIVO * existe y no es un fin de archivo, luego bloquea la secuencia para evitar que otros subprocesos corrompan la llamada a _IO_getc_unlocked().

La definición de macro de _IO_getc_unlocked() simplemente comprueba si el puntero de lectura está al final del punto de archivo y llama a __uflow si es o devuelve el carácter char en el puntero de lectura si no lo está.

Esto es estándar para todas las implementaciones de stdlib. Se supone que nunca debes mirarlo. De hecho, muchas implementaciones stdlib utilizarán lenguaje ensamblador para un procesamiento óptimo, que es aún más críptico.

2

Puedo recomendar encarecidamente The Standard C Library por P.J. Plauger. Proporciona antecedentes sobre el estándar y proporciona una implementación de cada función. La implementación es más simple que lo que verá en glibc o en un compilador de C moderno, pero todavía hace uso de macros como el _IO_getc_unlocked() que publicó.

La macro va a extraer un carácter de los datos almacenados en el búfer (que podría ser el búfer de ungetc) o leerlo de la secuencia (que puede leer y almacenar en búfer bytes múltiples).

+1

+1 por sugerir un libro. – saint

4

Yendo de pseudo-código a código real podemos descomponerlo:

if (there is a character in the buffer) 
    return (that character) 
else 
    call a function to refill the buffer and return the first character 
end 

Vamos a usar the ?: operator:

#define getc(f) (is_there_buffered_stuff(f) ? *pointer++ : refill()) 

Un poco más cerca:

#define getc(f) (is_there_buffered_stuff(f) ? *f->pointer++ : refill(f)) 

Ahora estamos casi allí. Para determinar si hay algo que ya esté almacenado, utiliza el puntero estructura de archivos y un puntero de lectura de la memoria intermedia

_fp->_IO_read_ptr >= _fp->_IO_read_end ? 

En realidad, esto pone a prueba la condición opuesta a mi pseudo-código, "es el buffer vacío", y si así, se llama __uflow(_fp) // "underflow", de lo contrario, sólo se llega directamente en el tampón con un puntero, se pone el carácter, y luego incrementa el puntero:

? __uflow (_fp) : *(unsigned char *) (_fp)->_IO_read_ptr++) 
Cuestiones relacionadas