AVR has either 8-bit or 16-bit and sometimes 32-bit registers.
For 8-bit in particular, it would get impractical if everything was into a single address space.
AVR has separate address spaces, that are accessed through different instructions:
Data Space: for accessing
RAM with run-time program data. Ex:
pop for managing the stack.
Program Space: as used by
JMP instructions to navigate through the machine code. Ex: interrupt vector table start at program space 0x0000.
IO Space: for communicating to devices, such as the MCU pins. Ex:
OUT Instructions for blinkinga led.
GCC comes with a libc for AVR, purposedly built for embedded, with no syscall, where program run on an MCU with direct access to everything and no operating system.
Every pin is having its own bit on memory. Pins are grouped by ports taking a whole byte, where most bits point to a single pin. Flipping a pin goes through bitwise operations onto the byte of that port. For instance,
PortB is 0x05 which end-up flipping a single bit.
For the I/O to actually take place, the direction of the port (In or Out) needs to be configured: to another port, usually one pin before: for
PortB it is
DdrB at 0x04.