To solve the severe external fragmentation problems caused by contiguous memory allocation, modern operating systems use non-contiguous memory allocation schemes. The two most prominent techniques are Paging and Segmentation.
Paging is a memory-management scheme that permits the physical address space of a process to be non-contiguous. It completely eliminates external fragmentation.
Because the pages of a single process can be scattered randomly across physical memory, the OS needs a way to track them. It maintains a Page Table for every process.
Advantage: No external fragmentation. Any free frame can be given to any process.
Disadvantage: Internal fragmentation still exists. If a process requires 72KB and the page size is 50KB, it will be allocated 2 pages (100KB), wasting 28KB.
While paging views memory as a flat array of fixed-size blocks (which is how the computer hardware views it), Segmentation is a memory-management scheme that supports the programmer's view of memory.
A logical address space is a collection of segments. Each segment has a name and a length. For example, a C program is typically compiled into several distinct segments:
Instead of breaking the program up into arbitrary fixed-size pages, the program is broken into these logical segments, which are of variable length.
If the offset $d$ is legally between 0 and the segment limit, the physical address is calculated as base + offset. If the offset is greater than the limit, the OS throws a Segmentation Fault (a very common error in C/C++ programming).
Modern OS architectures (like Intel x86) combine both methods. The logical address space is divided into segments, but then each segment is further divided into fixed-size pages. This provides the programmer-friendly logical view of segmentation, while utilizing the hardware-friendly fragmentation prevention of paging.