c - programming - stdout linux
¿Por qué stdout necesita un lavado explícito cuando se redirige al archivo? (3)
El comportamiento de printf()
parece depender de la ubicación de stdout
.
- Si
stdout
se envía a la consola,printf()
se almacena en la línea y se enjuaga después de que se imprime una nueva línea. - Si
stdout
se redirige a un archivo, el búfer no se vacía a menos que sefflush()
afflush()
. - Además, si se usa
printf()
antes de questdout
se redirija a un archivo, las escrituras subsiguientes (en el archivo) se guardan en línea y se enjuagan después de la nueva línea.
¿Cuándo está el buffer de línea fflush()
, y cuándo debe fflush()
?
Ejemplo mínimo de cada uno:
void RedirectStdout2File(const char* log_path) {
int fd = open(log_path, O_RDWR|O_APPEND|O_CREAT,S_IRWXU|S_IRWXG|S_IRWXO);
dup2(fd,STDOUT_FILENO);
if (fd != STDOUT_FILENO) close(fd);
}
int main_1(int argc, char* argv[]) {
/* Case 1: stdout is line-buffered when run from console */
printf("No redirect; printed immediately/n");
sleep(10);
}
int main_2a(int argc, char* argv[]) {
/* Case 2a: stdout is not line-buffered when redirected to file */
RedirectStdout2File(argv[0]);
printf("Will not go to file!/n");
RedirectStdout2File("/dev/null");
}
int main_2b(int argc, char* argv[]) {
/* Case 2b: flushing stdout does send output to file */
RedirectStdout2File(argv[0]);
printf("Will go to file if flushed/n");
fflush(stdout);
RedirectStdout2File("/dev/null");
}
int main_3(int argc, char* argv[]) {
/* Case 3: printf before redirect; printf is line-buffered after */
printf("Before redirect/n");
RedirectStdout2File(argv[0]);
printf("Does go to file!/n");
RedirectStdout2File("/dev/null");
}
El enjuague para stdout
está determinado por su comportamiento de almacenamiento en búfer. El almacenamiento en búfer se puede establecer en tres modos: _IOFBF
(almacenamiento en búfer completo: espera hasta fflush()
si es posible), _IOLBF
(almacenamiento en línea de línea: la nueva línea activa el lavado automático) y _IONBF
(escritura directa siempre utilizada). "El soporte para estas características está definido por la implementación y puede verse afectado por las setbuf()
y setvbuf()
". [C99: 7.19.3.3]
"Al inicio del programa, tres flujos de texto están predefinidos y no es necesario abrirlos explícitamente: entrada estándar (para lectura de entrada convencional), salida estándar (para escritura de salida convencional) y error estándar (para escribir salida de diagnóstico). Como se abrió inicialmente, la corriente de error estándar no está completamente almacenada en el búfer, la entrada estándar y las corrientes de salida estándar están completamente almacenadas en el búfer si y solo si se puede determinar que la transmisión no hace referencia a un dispositivo interactivo ". [C99: 7.19.3.7]
Explicación del comportamiento observado
Entonces, lo que sucede es que la implementación hace algo específico de la plataforma para decidir si stdout
va a ser buffer de línea. En la mayoría de las implementaciones de libc, esta prueba se realiza cuando la secuencia se utiliza por primera vez.
- El comportamiento n. ° 1 se explica fácilmente: cuando la transmisión es para un dispositivo interactivo, está almacenada en la línea, y el
printf()
se vacía automáticamente. - Ahora también se espera el caso n. ° 2: cuando redirigimos a un archivo, la transmisión se almacena completamente en el búfer y no se vacía, excepto con
fflush()
, a menos que se le escriban cargas de datos. - Finalmente, también entendemos el caso n. ° 3 para implementaciones que solo realizan la comprobación del fd subyacente una vez. Debido a que obligamos a que el buffer de stdout se inicialice en el primer
printf()
, stdout adquirió el modo de línea-buffer. Cuando intercambiamos el archivo fd para ir al archivo, todavía está almacenado en la línea, por lo que los datos se vacían automáticamente.
Algunas implementaciones reales
Cada libc tiene flexibilidad en la forma en que interpreta estos requisitos, ya que C99 no especifica qué es un "dispositivo interactivo", ni la entrada stdio de POSIX extiende esto (más allá de requerir que stderr esté abierto para la lectura).
Glibc. Ver filedoalloc.c:L111 . Aquí usamos
stat()
para probar si el fd es un tty y configuramos el modo de almacenamiento en búfer según corresponda. (Esto se llama desde fileops.c.) Stdout inicialmente tiene un búfer nulo, y se asigna en el primer uso de la secuencia en función de las características de fd 1.BSD libc. Muy similar, pero un código mucho más limpio a seguir! Vea esta línea en makebuf.c
Está combinando erróneamente funciones IO con búfer y sin búfer. Tal combinación debe hacerse con mucho cuidado, especialmente cuando el código tiene que ser portátil. (y es malo escribir un código no portable ...)
Sin embargo, es mejor evitar combinar IO con búfer y sin búfer en el mismo descriptor de archivo.
Buffer IO: fprintf()
, fopen()
, fclose()
, freopen()
...
IO sin búfer: write()
, open()
, close()
, dup()
...
Cuando usa dup2()
para redirigir stdout. La función no tiene conocimiento del buffer que fue llenado por fprintf()
. Por lo tanto, cuando dup2()
cierra el antiguo descriptor 1, no vacía el búfer y el contenido se puede descargar a una salida diferente. En su caso 2a fue enviado a /dev/null
.
La solución
En su caso, lo mejor es usar freopen()
lugar de dup2()
. Esto resuelve todos tus problemas:
- Vacía los búferes de la secuencia de
FILE
original. (caso 2a) - Establece el modo de almacenamiento en búfer según el archivo recién abierto. (caso 3)
Aquí está la implementación correcta de su función:
void RedirectStdout2File(const char* log_path) {
if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}
Desafortunadamente con IO con búfer no puede establecer directamente los permisos de un archivo recién creado. Debe usar otras llamadas para cambiar los permisos o puede usar extensiones glibc no portables. Ver la fopen() man page
.
No debe cerrar el descriptor del archivo, por lo tanto, elimine close(fd)
y cierre stdout_bak_fd
si desea que el mensaje se imprima solo en el archivo.