lb
lb
rd, off(rs1)
rd = mem[rs1 + off]
R
31–25funct7
24–20rs2
19–15rs1
14–12funct3
11–7rd
6–0opcode
I
31–20imm[11:0]
19–15rs1
14–12funct3
11–7rd
6–0opcode
S
31–25imm[11:5]
24–20rs2
19–15rs1
14–12funct3
11–7imm[4:0]
6–0opcode
B
31–25imm[12,10:5]
24–20rs2
19–15rs1
14–12funct3
11–7imm[4:1,11]
6–0opcode
U
31–12imm[31:12]
11–7rd
6–0opcode
J
31–12imm[20,10:1,11,19:12]
11–7rd
6–0opcode

lb loads a single byte from memory — the smallest amount you can read. A byte is 8 bits, and it is the unit memory is built from: every address names exactly one byte. Where lw reads 4 bytes and lh reads 2, lb reads just 1. It uses the familiar lb rd, offset(rs1) addressing (base register plus offset, explained on the lw page).

Since a register holds 32 bits but a byte is only 8, the upper 24 bits must be filled. lb sign-extends: it copies the byte's top bit across all the upper bits, preserving the value as a signed number. A byte holding -5 arrives in the register as a full 32-bit -5.

Here the signed-versus-unsigned choice matters more than usual, because the most common byte data is text — and text characters are unsigned. Many character codes have their top bit set, and loading one of those with the sign-extending lb turns it into a negative number, which then breaks comparisons against ordinary positive character values. For text you almost always want lbu (load byte unsigned), which fills the top with 0s. Save lb for data that is genuinely signed, like small positive-or-negative offsets.

Bytes sit one address apart, so you step through a byte array by adding 1 each time. A typical loop reads a byte, checks whether it is zero (the marker for end-of-text), acts on it, advances the pointer by 1, and repeats.