TRT
Sign in
easyFoundationsdesign

When building a command-line binary tool like xxd in C, why do we process 16-byte lines inside a 4 KB or 64 KB read buffer and guard printable ASCII checks?

Explain how block buffering amortizes I/O overhead, how 16-byte rows align with hexadecimal offsets, and why unsigned char is required for byte formatting and isprint.

TRT practice prompt — not a verified question from a named employer.

The Runtime Theory Team2 min read
Learn it firstProject: Command-Line Hex Dumper (xxd Clone)Engineer a high-performance command-line hexadecimal dumper in C, featuring POSIX getopt flag parsing, buffered byte streaming, pointer stepping, and canonical hex/ASCII formatting.

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–0xFF promoting to 0xFFFFFF80–0xFFFFFFFF in printf("%02x", ...)) and solves it with unsigned char / uint8_t.
  • Handles the final partial line (n < 16 bytes) by padding the missing hex slots (16 - n) * 3 spaces before printing the right-hand ASCII column.

Common mistakes

  • Storing raw binary bytes in a char array and seeing ffffffff printed for every byte >= 0x80.
  • Opening binary files on Windows or portable environments with "r" instead of "rb", causing the C runtime to translate 0x0D 0x0A ( ) or stop early on 0x1A (Ctrl+Z).
  • Forgetting to flush output or check ferror(fp) after the read loop ends.

This answer walks

Practice follow-ups

  1. 01What happens if you pass a signed char with value 0xFF (-1) to printf('%02x', c) or isprint(c)?
  2. 02How do you format the final partial row of a hex dump so the ASCII column still lines up with full 16-byte rows?

Not started

Sign in to save your learning progress.

Sign in to save