Por que uma string em C precisa de um byte além dos caracteres visíveis
Explicação da reserva de memória para strings em C, ligando o terminador nulo à indexação, à aritmética de ponteiros e ao contrato de validade.
Uma string em C precisa de um byte adicional porque sua representação inclui, depois do conteúdo, um caractere nulo final. Assim, uma cópia dinâmica não deve reservar apenas o comprimento do texto, mas esse comprimento mais um byte. A posição adicional faz parte da string entregue às operações de texto, embora não corresponda a um caractere visível do conteúdo.[1]
O byte adicional pertence à representação
Se o conteúdo ocupa N bytes, a reserva necessária para armazená-lo como string é N + 1 bytes. Os primeiros N bytes contêm o texto, enquanto o byte seguinte contém o caractere nulo final. Essa é a razão objetiva para o tamanho da alocação ser maior que o comprimento do conteúdo.[1]
A diferença entre comprimento e espaço reservado evita duas contas incompatíveis. O comprimento descreve o texto; a reserva precisa comportar o texto e sua fronteira final. Ao criar dinamicamente uma cópia, a orientação fornecida é reservar o comprimento mais um byte, exatamente para que o terminador permaneça dentro da região reservada.[1]
O byte adicional, portanto, não é uma margem arbitrária nem um espaço destinado a outro caractere. Ele tem uma função determinada na representação da string: guardar o caractere nulo final.[1]
A fronteira vista por índices e ponteiros
Em C, ptr[i] e *(ptr + i) designam o mesmo elemento. A primeira forma apresenta o acesso como indexação; a segunda mostra explicitamente o deslocamento feito a partir do ponteiro.[2]
Essa equivalência também se aplica à posição final da string. Se o conteúdo usa as posições de índice 0 até N - 1, o terminador ocupa a posição de índice N. Logo, ele pode ser alcançado tanto por ptr[N] quanto por *(ptr + N). A posição existe dentro da reserva somente quando foram disponibilizados N + 1 bytes.[1, 2]
A aritmética não cria o byte adicional. Ela apenas permite expressar qual elemento será acessado. O requisito de tamanho vem da representação da string: conteúdo mais terminador nulo. A equivalência entre as duas formas de acesso ajuda a enxergar que o terminador também ocupa uma posição concreta e indexada.[1, 2]

Um cálculo compacto da reserva
Considere um conteúdo que ocupa três bytes, representados por A, B e C. A reserva calculada é 3 + 1 = 4 bytes. Os índices 0, 1 e 2 recebem o conteúdo, e o índice 3 recebe o caractere nulo final. Essa quarta posição pode ser expressa como ptr[3] ou *(ptr + 3), pois as duas expressões designam o mesmo elemento.[1, 2]
Se fossem reservados somente três bytes, eles já estariam consumidos pelo conteúdo do exemplo. Não haveria uma posição reservada para o terminador no índice 3. Nesse caso, a área descrita não atenderia à regra de comprimento mais um byte exigida para a cópia da string.[1]
A conta geral pode ser mantida sem depender do valor específico do exemplo: para um comprimento N, a reserva é N + 1; o último byte reservado tem índice N.[1, 2]

O contrato do ponteiro entregue à função
Uma assinatura de função pode expressar que determinado argumento deve ser um ponteiro válido e não nulo para uma string. Esse contrato fortalece a verificação de tipos e permite ao compilador diagnosticar algumas violações, embora a evidência fornecida não afirme que todas elas sejam detectadas.[3]
Nesse contrato, “não nulo” e “válido para uma string” são requisitos distintos. Um valor de ponteiro apenas não nulo não satisfaz, por si só, a descrição completa apresentada pela assinatura. O argumento deve ser um ponteiro válido para a string que a operação espera receber.[3]
Relacionando esse contrato à reserva de memória, o objeto apontado precisa comportar a representação considerada: o conteúdo e o byte destinado ao caractere nulo final. Se a região reservada termina junto com o último byte do conteúdo, falta a posição adicional prevista pela regra de criação da string.[1, 3]
A validade relevante, portanto, não é uma propriedade isolada do endereço numérico. Ela precisa ser considerada em relação ao objeto entregue e à finalidade indicada pela função. Para uma função cujo argumento é declarado como ponteiro válido e não nulo para uma string, a região apontada deve corresponder a uma string com sua fronteira final representada.[1, 3]
Tamanho, acesso e operação formam uma única decisão
A decisão correta reúne três aspectos. Primeiro, calcular o comprimento do conteúdo e reservar mais um byte. Segundo, reconhecer que o último byte tem índice igual ao comprimento. Terceiro, entregar à operação um ponteiro que satisfaça o contrato de ponteiro válido e não nulo para uma string.[1, 2, 3]
Para operações usuais, a fonte recomenda preferir as funções padronizadas strlen, strcpy, strcat e strcmp. No caso específico da criação dinâmica de uma cópia, a regra documentada continua sendo reservar o comprimento do texto mais um byte para o caractere nulo final.[1]
A conteúdo relacionado do curso situa esse mecanismo na visão geral da trilha de C. Aqui, o limite da evidência é específico: equivalência entre indexação e aritmética de ponteiros, reserva do byte final e contrato de ponteiro válido para a string.[1, 2, 3]
Fontes consultadas
Obras e aulas usadas na redação deste guia. Os números no texto levam a elas.
- Waldemar Celes, Renato Cerqueira e José Lucas Rangel. Introdução a Estrutura de Dados 2ED: Com Técnicas de Programação em C. 2 capítulo 11
- Robert C. Seacord. The BOOK of Effective C: An Introduction to Professional C Programming. 2 capítulo 12
- Dawid Zalewski. Programming in Modern C with a Sneak Peek into C23. ACCU 2023, ACCU Conference, trecho de 00:18:18 a 00:19:07
