6

Tengo dos programas: primero.c y segundo.c

1. Cuando ejecuto ./primero obtengo esta salida:

Mensaje2\n
Mensaje1Mensaje3\n
Mensaje1Mensaje5Mensaje6

2. Cuando redirijo a un fichero usando ./primero > salida.txt en el fichero tengo una salida diferente:

Mensaje2\n
Mensaje1Mensaje5Mensaje6

No entiendo el por qué de esa salida ni por qué es distinta al redireccionar a un fichero.

primero.c

#include <stdio.h> 
main(){
   printf("Mensaje1"); 
   write(1,"Mensaje2\n",10); 
   if (fork()){
        printf("Mensaje3\n"); 
        execlp("segundo","segundo",0); 
        printf("Mensaje4");
   }
   printf("Mensaje5"); 
}

segundo.c

#include <stdio.h> 
main(){
    printf("Mensaje6"); 
}

ACTUALIZACIÓN:

Creo que al hacer un exec() se vacía el buffer. Cuando la salida es la estándar, el carácter \n vacía el buffer y lo imprime por pantalla porque es line-buffered. Sin embargo cuando está redireccionado al fichero no se imprime Mensaje3\n porque es fully buffered.

El contenido en hexadecimal del archivo salida.txt es:

00000000 4d 65 6e 73 61 6a 65 32 0a 00 4d 65 6e 73 61 6a |Mensaje2..Mensaj|
00000010 65 31 4d 65 6e 73 61 6a 65 35 4d 65 6e 73 61 6a |e1Mensaje5Mensaj|
00000020 65 36                                           |e6|
00000022
OscarGarcia
  • 26,999
  • 3
  • 26
  • 61
  • 1
    Cuando haces un `fork()` no sabes en qué orden se ejecutarán el resto de `printf`. Obviamente `Mensaje4` nunca se mostrará, en ningún caso, como efecto de usar `execlp`. – OscarGarcia Aug 21 '19 at 10:39
  • Sí, ese concepto lo tengo claro. Lo que menos entiendo es por qué cuando redirecciono la salida a un fichero no se escribe el `Mensaje3\n` @OscarGarcia – Fernando Navarro Aug 21 '19 at 10:43
  • A mí sí me lo guarda en el archivo. ¿Qué sistema operativo estás usando? Por cierto, deberías cambiar `execlp("segundo","segundo",0);` por `execlp("segundo", "segundo", NULL);`. En la documentación de [`execlp()`](https://linux.die.net/man/3/execlp) puedes leer: *The list of arguments must be terminated by a NULL pointer, and, since these are variadic functions, this pointer must be cast (char *) NULL.* – OscarGarcia Aug 21 '19 at 10:46
  • Uso MacOS. Lo del `execlp()` está bien supongo porque el código es de mi profesor de la universidad. @OscarGarcia – Fernando Navarro Aug 21 '19 at 10:51
  • No te fíes ciegamente de lo que diga un profesor, usa la documentación oficial o las páginas del manual de tu sistema operativo (`man execlp`). He tenido profesores que tenían conocimientos muy escasos (o superficiales) de lo que explicaban en clase. Por lo pronto no consigo reproducir tu problema bajo Linux, no dispongo de un Mac para hacer pruebas en él. – OscarGarcia Aug 21 '19 at 10:53
  • Estoy pensando que el problema podría venir de la forma en la que los Macs separan las líneas, mediante el uso de `\r` en vez de `\n`. Pero lo repito de nuevo, no tengo un Mac a mano para probar si realmente ese es el origen del problema. – OscarGarcia Aug 21 '19 at 10:59
  • Creo que sé la respuesta. Al hacer un `exec()` se vacía el buffer. Cuando la salida es la estándar, el carácter `\n` vacía el buffer y lo imprime por pantalla porque es line-buffered. Sin embargo cuando está redireccionado al fichero no se imprime `Mensaje3\n`. Muchas gracias de todas formas por el empeño @OscarGarcia – Fernando Navarro Aug 21 '19 at 11:00
  • ¿Podrías volcar el contenido en hexadecimal? Creo que realmente los mensajes están ahí, pero se están sobreescribiendo por un retorno del carro (sin nueva línea) que está produciendo `cat` al detectar el formato UNIX de manera errónea. Usa `hexdump -C < salida.txt`. – OscarGarcia Aug 21 '19 at 11:01
  • 00000000 4d 65 6e 73 61 6a 65 32 0a 00 4d 65 6e 73 61 6a |Mensaje2..Mensaj| 00000010 65 31 4d 65 6e 73 61 6a 65 35 4d 65 6e 73 61 6a |e1Mensaje5Mensaj| 00000020 65 36 |e6| 00000022 @OscarGarcia No sé cómo poner este comentario con mejor aspecto lo siento – Fernando Navarro Aug 21 '19 at 11:08
  • Por el volcado hexadecimal lo que dices tiene más sentido que un mal funcionamiento de `cat` por no detectar correctamente el fin de línea del archivo. Ciertamente es extraño el comportamiento y me gustaría saber qué lo produce si alguien con Mac, o que conozca bien el comportamiento, nos ilumina con enlaces a documentos :) PD: Añado esta pregunta a favoritos para no perderme actualizaciones. – OscarGarcia Aug 21 '19 at 11:11
  • PD: Este tipo de actualizaciones deberías hacerlas como edición de tu pregunta. Siempre que puedas aportar más datos para que otros usuarios puedan tener acceso a ellos, sin tener que leerse todos los comentarios, es mejor agregarlo al final de la pregunta. – OscarGarcia Aug 21 '19 at 11:13
  • PD2: Para que no se envíe a la salida estándar el carácter de fin de cadena de C (`\0`) deberías usar `write(1, "Mensaje2\n", 9);`. Son 9 caracteres, en vez de 10, porque `\n` ocupa un único carácter, no dos. – OscarGarcia Aug 21 '19 at 11:16
  • Fernando, he intentado abrir un chat en esta pregunta pero parece que no se puede por no tener la reputación necesaria. ¿Podrías agregar `setbuf(stdout, NULL);` al principio de tus programas para ver si deshabilitando el búfer en `stdout` forzamos el envío del texto? También podría funcionar si hacemos un `fflush()`. – OscarGarcia Aug 21 '19 at 11:20
  • 1
    Sí, ahora sí que imprime `Mensaje3\n`. @OscarGarcia – Fernando Navarro Aug 21 '19 at 11:22
  • Entonces te cedo la redacción de la respuesta con los datos del origen del problema que averiguaste más la solución que te he aportado. Yo mismo te daré el primer voto positivo cuando lo hagas :) Luego no olvides marcar tu propia respuesta como respuesta correcta (aunque por tu reputación creo que no podrás hacerlo hasta pasadas unas horas). – OscarGarcia Aug 21 '19 at 11:25
  • 2
    Qué gustazo ver la labor de investigación que hicisteis mano a mano con @Oscar ! – fedorqui Aug 22 '19 at 06:17
  • 1
    Gracias @fedorqui :D al final aprendí un comportamiento poco usual que nunca se me ha dado, pero que siempre es útil conocer ;) además de profundizar más en el funcionamiento del buffer de flujos de archivos. – OscarGarcia Aug 22 '19 at 09:17

1 Answers1

7

El problema sufrido es que al ejecutar la instrucción execlp() se sustituye el programa en ejecución por otro sin llamar a los procesos habituales de final de ejecución que vacía el buffer de salida de stdout si contiene aún información por enviar, por lo que si algo había pendiente de ser enviado a algún flujo de datos, se descarta.

Cuando la salida es la estándar, el carácter \n provoca el vaciado del buffer (envía el texto al terminal) porque es line buffered imprimiendo por pantalla todo aquello que tiene pendiente. Sin embargo, cuando la salida está redireccionada al fichero, no se imprime nada quedando pendiente en el buffer (en este caso Mensaje3\n) porque es fully buffered.

Más información sobre el comportamiento de las estrategias de buffering: https://www.gnu.org/software/libc/manual/html_node/Buffering-Concepts.html

  • Characters written to or read from an unbuffered stream are transmitted individually to or from the file as soon as possible.
  • Characters written to a line buffered stream are transmitted to the file in blocks when a newline character is encountered.
  • Characters written to or read from a fully buffered stream are transmitted to or from the file in blocks of arbitrary size.

En castellano:

  • Los caracteres leídos desde o escritos hacia un flujo de datos sin buffer (unbuffered) son transmitidos individualmente desde o hacia el archivo tan pronto como sea posible.
  • Los caracteres escritos a un flujo de datos con buffer de línea (line buffered) son transmitidos al archivo en bloques cuando se encuentra un carácter de nueva línea.
  • Los caracteres escritos hacia o leídos desde un flujo de datos de buffer completo (fully buffered) son transmitidos en bloques de tamaño arbitrario.

Para solucionar esto se debe añadir setbuf(stdout, NULL) al principio de los programas para deshabilitar el uso del buffer en stdout o bien provocar un fflush() antes de la llamada a execlp():

#include <stdio.h>
#include <unistd.h>

int main() {
  /* Desactivamos el buffer de stdout */
  setbuf(stdout, NULL);
  printf("Mensaje1");
  write(1, "Mensaje2\n", 10);
  if (fork()) {
    printf("Mensaje3\n");
    /* O bien forzamos el vaciado del buffer antes de llamar a execlp */
    fflush(stdout);
    execlp("segundo", "segundo", NULL);
    printf("Mensaje4");
  }
  printf("Mensaje5");
}
OscarGarcia
  • 26,999
  • 3
  • 26
  • 61