É tudo um pouco mais complicado do que as respostas simples dadas até agora.
Existem 2 aspectos: a máquina e o armazenamento em massa.
Na máquina:
Depende da arquitetura de hardware.
Em um PC, o endereçamento é por byte e você pode acessar um byte (8 bits), uma palavra (16 bits), uma palavra dupla (32 bits) e uma palavra-chave (64 bits).
Em outras arquiteturas, você pode ter acesso apenas a algum outro "blob" de tamanho para o tipo de dados da máquina. Por exemplo, no TMS320C40 você pode acessar palavras de 32 bits e bytes de 8 bits são compactados nessas palavras. Você pode empacotar os bytes para dentro e para fora, mas é um processo bastante lento que requer várias instruções de máquina.
Então, nesse TMS320C40, o compilador C tem um tipo de caractere nativo que é de 32 bits!
(quando estiver programando em C, nunca ASSUME que um caractere é de 8 bits. Leia o manual do seu compilador, especialmente se estiver fazendo programação embutida).
As coisas ficam ainda mais complicadas quando o endian-ness entra em jogo, existem dois arranjos comuns: little e big endian, isso descreve como o byte é organizado para se encaixar em uma quantidade maior (normalmente o tamanho da palavra nativa). Então, por exemplo, em uma máquina de 32 bits, você pode encontrar os bytes organizados assim:
Endereço X: Byte 0, Byte 1, Byte 2, Byte 3
Endereço X + 4: Byte 4, Byte 5, Byte 6, Byte 7
OR
Endereço X: Byte 3, Byte 2, Byte 1, Byte 0
Endereço X + 4: Byte 7, Byte 6, Byte 5, Byte 4
(E fica ainda mais complexo porque os bits em um byte também têm endian).
Principalmente, esse tipo de coisa só surge como uma preocupação para os projetistas de hardware. Mas se você tem que escrever drivers de dispositivos e coisas que falam com hardware que é através de registros mapeados na memória, isso se torna um grande problema.
Um exemplo simples pode ser suficiente:
Despejar um bloco de memória no endereço X pode apresentar um fluxo de bytes:
01 02 03 04 05 06 07 08
MAS despejar esse mesmo bloco do mesmo endereço e apresentar números inteiros de 16 bits (hexadecimais) pode ser apresentado como:
0201 0403 0605 0807
E despejar novamente do mesmo endereço que os inteiros de 32 bits em hexadecimal podem ser apresentados como:
04030201 08070605
Isso causa uma grande confusão para os não iniciados, porque tudo depende do endian-ness, e o método (ordem dos bytes) usado para fazer quantidades maiores de menores.
Geralmente, linguagens de alto nível escondem esse nível de horror, mas podem ser importantes para coisas como estruturas de dados de sobreposição e, novamente, registros de controle de dispositivos mapeados pela memória.
Armazenamento em massa.
Felizmente, a vida fica mais fácil.
Pense no seu armazenamento em massa como um monte de bytes, que podem ser acessados, e a máquina cuidará magicamente de tudo. O termo comum usado é coisa de arquivos como um "fluxo", onde você começa no início e o fluxo entra. (Isso convenientemente ignora o acesso aleatório). A menor parte na qual você pode dividir o fluxo do arquivo é um byte.
Se uma máquina deseja armazenar quantidades maiores (palavras de 16 bits, etc), pode ou não fazer algum nível de transformação para obter isso nos bytes que vão para o armazenamento.
Advertências
Todos os itens acima estão relacionados a coisas subjacentes de baixo nível - bytes, palavras e assim por diante.
Os programas fazem uso disso de todas as maneiras. Então, por exemplo, você terá CHARACTERS representados por bytes se eles se encaixarem perfeitamente em ASCII simples (ou até EBCDIC para aqueles com memórias longas). Os modernos sistemas de caracteres Unicode podem usar caracteres largos (geralmente são 16 bits), mas existem muitos sistemas de codificação para unicode. A página da Wikipedia sobre Unicode é bastante instrutiva.
A convenção em C de assumir CHARACTER = BYTE é hoje em dia, equivocada e equivocada. Seu melhor para coisa de "char" é um sinônimo de "byte" - a menos que sua máquina / compilador diga o contrário (veja acima). Bons programas C geralmente definem um conjunto de tipos preferidos como "UINT8" - inteiro de 8 bits sem sinal, "SINT8" - inteiro de 8 bits assinado, e assim por diante, para que o programa escrito se torne tão independente quanto possível das peculiaridades de o compilador específico e o hardware subjacente.
Para a pergunta específica: Como os caracteres são armazenados? A resposta é: depende. Freqüentemente, caracteres ASCII que cabem em um byte são armazenados como um byte. Caracteres largos são freqüentemente armazenados como palavras de 16 bits. Mas o unicode pode implementar caracteres amplos ou um de qualquer número de sistemas de codificação, nesse caso os caracteres podem ocupar de 1 a 4 bytes, dependendo do caractere.