fgetc is a C stdio library function that uses its input buffering for FILE *stdin.
You can use strace to see what system calls your process makes. read is an actual system call; the libc wrapper for it just passes its args on to the kernel. (On x86-64, by doing mov eax, 1 (__NR_read) ; syscall ; ret, and maybe). So your read program will just do that one system call (after libc startup), but fgetc has to make its own read call to get bytes from stdin.
On my x86-64 Arch GNU/Linux system:
$ strace -o fgetc.tr ./a.out
hello<enter>
$ tail fgetc.tr
...
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x753744d52000, 278107) = 0
fstat(0, {st_mode=S_IFCHR|0600, st_rdev=makedev(0x88, 0x9), ...}) = 0
getrandom("\x62\xf6\x57\x14\x0d\xb2\x41\x75", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x59eadd786000
brk(0x59eadd7a7000) = 0x59eadd7a7000
read(0, "hello\n", 1024) = 6
lseek(0, -5, SEEK_CUR) = -1 ESPIPE (Illegal seek)
exit_group(0) = ?
The last brk() calls (finding the current break then moving it) might have been before main started, or might have been allocating space on demand for the stdin buffer on first use. Even the fstat(0, ...) might have been part of the fgetc, since that's also querying what kind of file it is (in this case a character device file.)
Because stdin is buffered (by default), glibc uses a 1024-byte read system call.
fd 0 was connected to a terminal so the system-call blocked until I hit enter, because the terminal is in "cooked" mode1 and there weren't already any queued keystrokes / input. If it had been a regular file, the read system call wouldn't stop at newlines, only EOF or the requested size. (Or if the requested size and the file were huge, at some kernel-chosen size limit for a single read.)
TTYs have a buffer so you can type even when there isn't a process blocked on a read system call. (And in "cooked" mode there's even line editing, like backspace, before the end-of-line character (normally newline) or end-of-file character (normally ctrl-D) submits the line.)
If the TTY buffer isn't empty when your process exits, those characters will still be there for the shell to read from it, since your process and the shell both have their stdin connected to the same TTY.
Footnote 1: Cooked as oppose to raw mode like your shell uses, or like an editor like vim would use. You could use stty -a < /dev/pts/9 from another terminal while your process is running vs. while you're at the shell prompt to see the different settings. Where /dev/pts/9 is the tty for the xterm or SSH session or whatever you're using. One easy way to find out the right path is ls -l /proc/self/fd and look at the symlink names for where ls's stdin/out/err refer to.
And BTW, stty operates on its stdin, printing output if any on its stdout, that's why we redirect from the terminal we want to query or set.
The shell itself puts the terminal in "cooked" mode before starting a command, because that's the default environment for stuff like cat >> foo.txt which lets you type something with line-editing into a file, or programs that print a prompt and wait to read a multi-character response. So strace on your own program won't show ioctl system calls for that.
fgetcreturns anint, not acharhello:" --> Did you enter 5 keystrokeshelloor 6hello\n? <Enter> is a key too.