Some more info on x86 that might help clarify, a machine "word" is defined as 16 bits/2 bytes. On many (almost all others) architectures, a "word" is 32 bits/4 bytes. That means, on x86:
byte = 8 bits, smallest unit the CPU can address when communicating with memory, that is to say, to access bits, you have to read at least a byte, toggle the bits you want, then write back at least a whole byte
word = 2 bytes, or 16 bits
dword = 4 bytes, or 32 bits
qword = 8 bytes, or 64 bits
On some architectures, address space can refer to words, so a machine with 32 address lines (4 billion addresses) accessing a 4 byte word at each address totals to 16 GB of physical ram. On x86, each address is exactly one byte, so 32 bit address space = 4 billion bytes (4GB).
How much it reads at a given address is determined by the instruction. A byte instruction like "mov al, [0x00000000]" will read only that byte. "mov ax, [0x00000000]" will move 2 bytes from 0x00000000-0x00000001. "mov eax, [0x00000000]" will load the 4 bytes from 0x00000000-0x00000003. "mov rax, [0x00000000]" will load 8 bytes from 0x00000000-0x00000007.
You can access bytes, but it's not efficient use of cycles. Also word or higher reads that cross a word boundary are broken into two address accesses (for example mov eax, [0x00000001] has to read from both 0x00000000 for the first 3 bytes and 0x00000004 for the last byte). This is "word alignment".
This is all you need to know as a software programmer.
To further complicate matters you have cache. Regardless of a byte read or a quadword read, the CPU only physically reads/writes memory in blocks of 16 bytes or so, known as a "cache line". This is completely transparent to the programmer, the only thing you need to know, is that it's there, how it works, and how to optimize your reads/writes so as not to cause cache flushes/misses/reloads, esp due to the way multiples of addresses share cache lines (the term "cache thrashing") etc
The only reason you can't read 0x00000000 in practice is that it's a "NULL" address purely by convention alone; the first entry in the page table (or several) of every process is intentionally left invalid (not backed by real memory) for debugging purposes so you know when you've accessed a zero memory address or uninitialized pointer. Hence the term "null pointer" and related access violations. This is purely by convention and there is no real physical reason for not being able to have valid data at address 0x00000000.
Behind the scenes, you have virtual memory mapping: page tables and translation look aside buffers are special data structures in RAM managed by the OS and defined by the x86 architecture, that perform virtual to physical memory lookups, handle page faults behind the scenes, etc. These structures, along with file system buffers, and memory mapped hardware, make the majority of memory taken up by the OS kernel.
All processes have their own page table and page table directory, managed by the OS, this are the most important part of a task switch, in addition to saved CPU registers, when the OS multitasks. When the address of the page table is swapped (system register CR3 on x86 if I recall) what WAS at 0x00000000-0x7FFFFFFF (your application) disappears and is instantly replaced by the contents of another process. This is multi tasking. 0x80000000-0xFFFFFFFF is the same in all processes page tables, this is the OS, and is protected memory (ring 0 on x86). Trying to look at this memory in any form results in ???????? in a debugger. You cannot view this memory unless you are doing kernel/driver development with the Win32 DDK, and any attempt to access in a program will cause a access violation and crash your app. The process of mapping virtual memory addresses to generate the correct physical address line signal to the busses is handled entirely in hardware, automatically, by the CPU (it's hardware memory management unit). All the OS needs to do is follow the protocol for setting up and loading the page tables correctly.
Why is this complex mechinism used at all and why not just straight physical addressing? Notice how every application has the same address space (the virtual one, the only one we ever need be concerned with unless writing an OS) and how multi tasking, system sharing, data/code security, privileges, etc becomes possible. Not having this, and having a straight flat physical address space = DOS, where any app can write to the interrupt vector table, all apps have to fit in the address space at the same time and start at various locations, etc. Virtual memory addressing solves all these problems very neatly. You need not worry about an application altering another applications memory (unless deliberately through system calls), messing with the OS (because it can't see it), all processes start at the same address in their own virtual address sandbox and have the whole address space to themselves, etc.
As a programmer (using 32 bit example), all processes have the same memory addresses (virtual), that is to say 0x00000000 to 0x7FFFFFFF, this is it's virtual address space and is identical for all apps. Not all of this space is mapped at once. Initially only a very very small portion is mapped, from what the OS allocates (in multiples of 4k) to load the image (.exe), it's resources, initial stack, .dlls, initial heap, etc. Accessing any address in that range that isn't valid (nothing was ever allocated there) causes an invalid page fault and the process to be terminated. Accessing memory with incorrect permission (writing to read only) causes a fault. Accessing a page that is valid, but marked "not present" causes the same exact page fault, but the OS can examine the page table and look to see that there is in fact something there, it's just in the page file. Then it allocates or steals a page from another process (after writing it's contents to the swap file and marking it not present in the donor process), retrieves 4k from the swap file, and resumes your program like the fault never happened. Faults are really just built in hardware interrupts caused by the CPU itself, based on information in the page tables, to transfer control to the OS, which then investigates why the fault occurred, and takes action (either kill the process or retrieve the page from swap, etc).
New pages are mapped in as required. Keyword "new" for example, will return memory if there is some free memory in the heap, or if the heap is used up, call the OS to request more RAM be mapped to the current process, THEN return the new memory at the newly used virtual. The OS merely makes a change to the page table, and instantly a 4k page of something appears in your address space. This can be memory mapped hardware, a shared read only section of library code (1 physical 4k page that is written to all page tables), heap memory requested with malloc/new, etc. The whole address space can't be used because the OS needs to be in RAM on a context change. That is, the ram from 0x80000000 to 0xFFFFFFFF is address space that has OS and hardware resource ready immediately when the privilege changes from a system call, interrupt, etc. The CPU has to immediately vector to OS code from the currently running process, thus the OS must be immediately visible in the virtual address space of ALL running processes.
This is what is referred to as 2/2 GB split, or optional 3/1 GB, and the reason the application does not have 4 GB to itself. This virtual address space crowding is *the* biggest reason for 64 bit, not necessarily physical ram amount (which already went to 16+ GB in 32 bit with PAE). This is what causes System Properties to only show 2-3GB ram when you have 4 GB installed. The push for 64 bit wasn't so much for physical RAM stick capacity as it was for cramped virtual memory address space. You could have 16 GB physical ram on a 32 bit CPU, but the application can only see it in a virtual window of 2 GB at a time (from 0x00000000 to 0x7FFFFFFF). 64 bit address space eliminates this problem even if you only have 4GB RAM installed.
PS: No regard for knowledge levels here, I'm assuming there is some level of understanding of programming/OS to be asking this question
