cosmopolitan
cosmopolitan copied to clipboard
Bug: printf() isn't working as expected
Contact Details
contact me on here
What happened?
hello. found a weird issue with printf, I don't know why this happens, but if I compile my example calculator app, it doesn't print to stdout until you fill out all needed stuff, here's the C code.
#include<stdio.h>
#include<stdlib.h>
int main(){
printf("welcome in calculator.");
char *choice=malloc(sizeof(char));
printf("type in the operator");
scanf("%c",choice);
switch(*choice)
{
case '/':
free(choice);
printf("first number");
int64_t *num1=malloc(sizeof(int64_t));
int64_t *num2=malloc(sizeof(int64_t));
printf("first number");
scanf("%lld",num1);
printf("second number");
scanf("%lld",num2);
int64_t *result=malloc(sizeof(int64_t));
*result =*num1 / *num2;
free(num1);
free(num2);
printf("%lld",*result);
free(result);
break;
case '+':
free(choice);
int64_t *Num1=malloc(sizeof(int64_t));
int64_t *Num2=malloc(sizeof(int64_t));
printf("type in first number");
scanf("%lld",Num1);
printf("second number");
scanf("%lld",Num2);
int64_t *Result=malloc(sizeof(int64_t));
*Result= *Num1+ *Num2;
free(Num1);
free(Num2);
printf("%lld",*Result);
free(Result);
break;
case '*':
free(choice);
int64_t *n1=malloc(sizeof(int64_t));
int64_t *n2=malloc(sizeof(int64_t));
scanf("%lld",n1);
scanf("%lld",n2);
int64_t *res=malloc(sizeof(int64_t));
*res= *n1 * *n2;
free(n1);
free(n2);
printf("%lld",*res);
free(res);
break;
case '-':
free(choice);
int64_t *N1=malloc(sizeof(int64_t));
int64_t *N2=malloc(sizeof(int64_t));
printf("type in the first number");
scanf("%lld",N1);
printf("type in second number");
scanf("%lld",N2);
int64_t *Res=malloc(sizeof(int64_t));
*Res= *N1- *N2;
free(N1);
free(N2);
printf("%lld",*Res);
free(Res);
break;
}
}
Version
cosmocc (GCC) 14.1.0
What operating system are you seeing the problem on?
Mac
Relevant log output
No response
cosmos seems to have stdout line-buffered, just use setbuf(stdout, NULL) or some \n in your strings.
Why they do this? Is there any way to fix it so that will not be needed?
libc/stdio/stdout.c contains the reason why. You can maybe just change the mode there, but I don't know if it will work. (just adding one setbuf at the start of your code is probably easier, or you could use fflush instead)
With glibc in ttys, stdout is also line buffered. glibc appears to flush stdout when stdin is read from. Personally, I wouldn't depend on this behavior as it doesn't appear to be guaranteed by C, but this may be something we can improve.
https://stackoverflow.com/questions/66815208/how-is-printf-getting-flushed-before-scanf-is-executed
Upon further consideration, I'm not convinced we should change this. musl has good reasoning why they don't:
Rich Felker:
There's nothing detectable here because there's nothing wrong with the program; the bug is in the programmer's expectation that the output be visible. It's possible to implement the behavior the programmer here desired, the optional flushing of line-buffered output streams before reading input. This would not help detect the bug in expectaions though; it would just help mask it. The reason this behavior is not present in musl is because it does not scale with significant numbers of stdio streams open, and can even produce deadlock conditions in multithreaded programs where there is no semantic deadlock but the additional flushing produces an extraneous operation on a stream in a way that causes deadlock.
https://inbox.vuxu.org/musl/CAAMnvke+NhDbGahX5mbzOpsWEPzPf4K-0dYE-ZMHJTr5xSEGhQ@mail.gmail.com/T/
To reproduce the deadlock condition I made a demo program, save to wr_stdio.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(){
const unsigned pipesize = fcntl(1, F_GETPIPE_SZ);
fprintf(stderr, "pipesize %u\n", pipesize);
// fill up the stdout pipe
static char buf[4096];
for(unsigned i = 0; i < (pipesize / sizeof(buf)); i++) {
write(1, buf, sizeof(buf));
}
// print without flushing
printf("write without flushing");
fprintf(stderr, "the printf itself is harmless\n");
getchar(); // reading should be
fprintf(stderr, "glibc never gets here as we are deadlocked writing to stdout\n");
}
Compile and run with cosmocc.
cosmocc -o wr_stdio_cosmo wr_stdio.c && ./wr_stdio_cosmo | sleep 10000
After you press ENTER, the final output should look something like:
pipesize 65536
the printf itself is harmless
glibc never gets here as we are deadlocked writing to stdout
Now if we do the same with gcc with glibc:
gcc -o wr_stdio_gcc wr_stdio.c && stdbuf --output=L ./wr_stdio_gcc | sleep 10000
pipesize 65536
the printf itself is harmless
To easily reproduce, stdbuf --output=L forces the stdout buffering to line mode as glibc doesn't use line mode with pipes.
POSIX defines stdout as buffered. You need to use fflush() if you want to print a prompt to your terminal before reading.