Eric Larsen Eric Larsen - 4 months ago 12
Brainfuck Question

getchar() taking the last char from previous printf()?

I'm writing a compiler/interpreter for the esoteric language brainf*ck (I'm not too sure on StackOverflow's profanity policy, so I'll censor myself until somebody tells me I don't have to), and I'm running into a very mysterious (to me, at least) bug in which the last character from my debugging output is being accepted as an input to the brainf*ck program being run. The following is the source for the interpreter: brainf*ck.c, the source for the program: OR.bf, and a partial print of the output from running OR.bf through the brainf*ck executable. (Many apologies in advance for the messy code. I wrote the interpreter in less than a day as a fun project.)
Thanks in advance for the help!

brainf*ck.c:

#include <stdio.h>
#include <string.h>

char* readCmd(int, char* []);
void readProg(FILE*,char[]);
int checkSyntax(char[]);
void init(char*, char[], char[]);
void run(unsigned char**, unsigned char**);
void eval(unsigned char**, unsigned char**);


int main(int argc, char* argv[])
{
unsigned char data[30000] = {0};
unsigned char* dptr = &(data[0]);
unsigned char** dpptr = &dptr;
unsigned char inst[30000] = {0};
unsigned char* iptr = &(inst[0]);
unsigned char** ipptr = &iptr;
char* cmd = readCmd(argc, argv);
FILE* src = fopen(cmd, "r");
if(src != NULL)
{
readProg(src, inst);
if(checkSyntax(inst))
{
run(ipptr, dpptr);
}
else
{
printf("Syntax error. Please fix your code\n");
}
}
else
{
printf("File '%s' not found.\n", cmd);
}
fclose(src);
return 0;
}


char* readCmd(int argc, char** argv)
{
char* cmd = NULL;
if(argc == 2)
{
cmd = argv[1];
}
else
{
cmd = "";
printf("Usage: %s <filename>.bf\n", argv[0]);
}
return cmd;
}


void readProg(FILE* src, char inst[])
{
int i = 0;
while(!feof(src))
{
char c = fgetc(src);
if(c == '<' || c == '>' || c == '+' || c == '-' || c == '.' || c == ',' || c == '[' || c == ']')
{
inst[i] = c;
i++;
}
}
}


int checkSyntax(char inst[])
{
int open = 0;
int i = 0;
for(i = 0; i < strlen(inst); i++)
{
if(inst[i] == '[')
open++;
if(inst[i] == ']')
open--;
}
return !open;
}


void init(char* cmd, char instruct[], char data[])
{
return;
}


void run(unsigned char** ipptr, unsigned char** dpptr)
{
while(**ipptr != 0)
{
eval(ipptr, dpptr);
(*ipptr)++;
}
return;
}


void eval(unsigned char** ipptr, unsigned char** dpptr)
{
//fprintf(log, "eval: %c %i %x %x\n", **ipptr, **dpptr, *ipptr, *dpptr);
printf("eval: %c %i %x %x\n", **ipptr, **dpptr, *ipptr, *dpptr);
getch();
int open = 0;
switch(**ipptr)
{
case '>':
(*dpptr)++;
break;
case '<':
(*dpptr)--;
break;
case '+':
//printf("b: dptr:%x *dptr:%i\n", *dpptr, **dpptr);
(**dpptr)++;
//printf("a: dptr:%x *dptr:%i\n", *dptr, **dpptr);
break;
case '-':
(**dpptr)--;
break;
case '.':
putchar(**dpptr);
break;
case ',':
**dpptr = getchar();
break;
case '[':
if(**dpptr)
{
//(*ipptr)++;
}
else
{
open++;
do {
(*ipptr)++;
if(**ipptr == '[')
open++;
if(**ipptr == ']')
open--;
} while(open);
}
break;
case ']':
if(**dpptr)
{
open = 1;
do {
(*ipptr)--;
if(**ipptr == ']')
open++;
if(**ipptr == '[')
open--;
} while(open);
}
break;
default:
break;
}
return;
}


OR.bf:

,------------------------------------------------>
,------------------------------------------------<
[[-]>>+<<]
>
[[-]>+<]
>
>+<
[[-]>->+++++++++++++++++++++++++++++++++++++++++++++++++<<]>
[[-]>++++++++++++++++++++++++++++++++++++++++++++++++<]>
.


output:

user@userland ~/brainf*ck
$ brainf*ck.exe OR.bf
eval: , 0 22149c 2289d0
1
eval: - 49 22149d 2289d0
eval: - 48 22149e 2289d0
eval: - 47 22149f 2289d0
eval: - 46 2214a0 2289d0
eval: - 45 2214a1 2289d0
eval: - 44 2214a2 2289d0
eval: - 43 2214a3 2289d0
eval: - 42 2214a4 2289d0
eval: - 41 2214a5 2289d0
eval: - 40 2214a6 2289d0
eval: - 39 2214a7 2289d0
eval: - 38 2214a8 2289d0
eval: - 37 2214a9 2289d0
eval: - 36 2214aa 2289d0
eval: - 35 2214ab 2289d0
eval: - 34 2214ac 2289d0
eval: - 33 2214ad 2289d0
eval: - 32 2214ae 2289d0
eval: - 31 2214af 2289d0
eval: - 30 2214b0 2289d0
eval: - 29 2214b1 2289d0
eval: - 28 2214b2 2289d0
eval: - 27 2214b3 2289d0
eval: - 26 2214b4 2289d0
eval: - 25 2214b5 2289d0
eval: - 24 2214b6 2289d0
eval: - 23 2214b7 2289d0
eval: - 22 2214b8 2289d0
eval: - 21 2214b9 2289d0
eval: - 20 2214ba 2289d0
eval: - 19 2214bb 2289d0
eval: - 18 2214bc 2289d0
eval: - 17 2214bd 2289d0
eval: - 16 2214be 2289d0
eval: - 15 2214bf 2289d0
eval: - 14 2214c0 2289d0
eval: - 13 2214c1 2289d0
eval: - 12 2214c2 2289d0
eval: - 11 2214c3 2289d0
eval: - 10 2214c4 2289d0
eval: - 9 2214c5 2289d0
eval: - 8 2214c6 2289d0
eval: - 7 2214c7 2289d0
eval: - 6 2214c8 2289d0
eval: - 5 2214c9 2289d0
eval: - 4 2214ca 2289d0
eval: - 3 2214cb 2289d0
eval: - 2 2214cc 2289d0
eval: > 1 2214cd 2289d0
eval: , 0 2214ce 2289d1
eval: - 10 2214cf 2289d1


The above output is of the following format:
eval: <*instruction ptr> <*data ptr>

(SO seems to be mangling the above line, so just refer to the beginning of eval() in brainf*ck.c.

As you can see, the second to last and last lines of output indicate that getchar() is (for some reason) getting the newline character from the end of the second to last line instead of waiting for user input (as it did at line 1).

I'm more interested in why this bug has cropped up in my program than how to fix it, because it makes me think that I don't have as good a handle as I thought I had on input streams in C, but suggestions on a fix are (of course) welcome.

Answer

C's stdin is line-buffered. This means that when you read input from stdin, it reads a line from the user (in this case, you entered the string "1\n") and returns as much of it as asked for, storing the rest in the buffer. Since you only asked for one character, it returns '1' and stores the '\n' in the buffer. Next time you ask for a character, it will return the '\n'.

In standard C, there's no way to prevent this behavior. The best (universally portable) option is to read an entire line, print a diagnostic if the user tried to enter more than one character, and just take the first character of that line. That way, the next time it asks for a character, it will be guaranteed to prompt you. This also allows you some parsing room, if you wanted to add support for, say, users typing in \n to get a newline. It's not necessary, but it could be nice.

If you want the program to take one keystroke and continue executing, without waiting for the user to enter an entire line, you'll need to use a platform specific library. On Unix systems (Linux, OS X, etc.) you should probably look into ncurses, but there are some less heavyweight solutions. On Windows I think all you need is the getch (or is it getche?) function. I did this kind of stuff a while ago and I could probably fish it up if you wanted, but you can probably find it online (the same way I did).

Comments