A strong answer
A hex dumper formats binary files in rows of 16 bytes (0x10) because 16 is a power of two that fits comfortably on an 80-column terminal while making every row's starting offset end in 0 in hexadecimal (00000000, 00000010, 00000020). However, calling fread or read for 16 bytes at a time—or 1 byte at a time—would incur millions of function or system call transitions on a large file. Reading in chunks of 4 KB to 64 KB (matching OS page and disk block sizes) into a contiguous buffer and stepping a pointer 16 bytes at a time processes gigabytes per second in L1 cache.
The single most dangerous bug when writing a hex dumper in C is using plain char instead of unsigned char (uint8_t) for the byte buffer. On x86-64, plain char is signed (-128 to 127). When a byte with value 0xFF (-1) is passed to a variadic function like printf("%02x", buf[i]), C's default argument promotion sign-extends -1 to the 32-bit integer 0xFFFFFFFF, printing 8 characters (ffffffff) instead of 2 (ff) and breaking the entire column layout! Worse, passing a negative char (other than EOF) to <ctype.h> functions like isprint(c) is undefined behavior because isprint indexes a lookup table with c. Always declare binary buffers as unsigned char (or cast (unsigned char)c), or check printable ASCII explicitly with b >= 0x20 && b <= 0x7E.
What the interviewer is listening for
- Explains the two-level buffering design: large 4 KB–64 KB block reads for I/O throughput, sliced in memory into 16-byte display lines.
- Identifies the sign-extension bug (
0x80–0xFFpromoting to0xFFFFFF80–0xFFFFFFFFinprintf("%02x", ...)) and solves it withunsigned char/uint8_t. - Handles the final partial line (
n < 16bytes) by padding the missing hex slots(16 - n) * 3spaces before printing the right-hand ASCII column.
Common mistakes
- Storing raw binary bytes in a
chararray and seeingffffffffprinted for every byte>= 0x80. - Opening binary files on Windows or portable environments with
"r"instead of"rb", causing the C runtime to translate0x0D 0x0A() or stop early on0x1A(Ctrl+Z). - Forgetting to flush output or check
ferror(fp)after the read loop ends.