Como as matrizes de caracteres devem ser usadas como seqüências de caracteres?

10

Eu entendo que seqüências de caracteres em C são apenas matrizes de caracteres. Então, tentei o código a seguir, mas ele fornece resultados estranhos, como saída de lixo ou falhas no programa:

#include <stdio.h>

int main (void)
{
  char str [5] = "hello";
  puts(str);
}

Por que isso não funciona?

Ele é compilado corretamente gcc -std=c17 -pedantic-errors -Wall -Wextra.


Nota: Esta postagem deve ser usada como uma FAQ canônica para problemas decorrentes de uma falha na alocação de espaço para um terminador NUL ao declarar uma string.

Lundin
fonte

Respostas:

12

AC string é uma matriz de caracteres que termina com um terminador nulo .

Todos os caracteres têm um valor de tabela de símbolos. O terminador nulo é o valor do símbolo 0(zero). É usado para marcar o final de uma string. Isso é necessário, pois o tamanho da string não é armazenado em nenhum lugar.

Portanto, toda vez que você alocar espaço para uma sequência, você deve incluir espaço suficiente para o caractere terminador nulo. Seu exemplo não faz isso, apenas aloca espaço para os 5 caracteres de "hello". O código correto deve ser:

char str[6] = "hello";

Ou, de forma equivalente, você pode escrever um código de auto-documentação para 5 caracteres mais 1 terminador nulo:

char str[5+1] = "hello";

Ao alocar memória para uma sequência dinamicamente em tempo de execução, você também precisa alocar espaço para o terminador nulo:

char input[n] = ... ;
...
char* str = malloc(strlen(input) + 1);

Se você não anexar um terminador nulo no final de uma sequência, as funções da biblioteca que esperam que uma sequência não funcione corretamente e você receberá erros de "comportamento indefinido", como saída de lixo ou falhas no programa.

A forma mais comum para escrever um caractere nulo terminador em C é usando um chamado "sequência de escape octal", procurando assim: '\0'. Isso é 100% equivalente à gravação 0, mas \serve como código de auto-documentação para indicar que o zero é explicitamente destinado a ser um terminador nulo. Código como if(str[i] == '\0')verificará se o caractere específico é o terminador nulo.

Observe que o termo terminador nulo não tem nada a ver com ponteiros nulos ou com a NULLmacro! Isso pode ser confuso - nomes muito semelhantes, mas significados muito diferentes. É por isso que o terminador nulo às vezes é chamado de NULum L, para não ser confundido com NULLponteiros nulos. Veja as respostas a esta pergunta do SO para obter mais detalhes.

O "hello"código em seu código é chamado literal de string . Isso deve ser considerado como uma sequência de somente leitura. A ""sintaxe significa que o compilador anexará um terminador nulo no final da cadeia literal automaticamente. Portanto, se você imprimir sizeof("hello"), receberá 6, não 5, porque obtém o tamanho da matriz, incluindo um terminador nulo.


Ele é compilado corretamente com o gcc

De fato, nem mesmo um aviso. Isso ocorre devido a um detalhe / falha sutil na linguagem C que permite que as matrizes de caracteres sejam inicializadas com uma literal de cadeia de caracteres que contenha exatamente quantos caracteres houver espaço na matriz e, em seguida, descarte silenciosamente o terminador nulo (C17 6.7.9 / 15) O idioma está propositalmente se comportando assim por razões históricas, consulte Diagnóstico inconsistente do gcc para inicialização de strings para obter detalhes. Observe também que o C ++ é diferente aqui e não permite que esse truque / falha seja usado.

Lundin
fonte
11
Você deve mencionar o char str[] = "hello";caso.
precisa
@Jabberwocky Este é um wiki da comunidade, fique à vontade para editar e contribuir.
Lundin
11
... e talvez também o char *str = "hello";... str[0] = foo;problema.
precisa
Talvez estenda a implicação de usar sizeofpara seu uso em um parâmetro de função, especialmente quando definido como uma matriz.
Weather Vane
O @WeatherVane deve ser coberto por outra FAQ aqui: stackoverflow.com/questions/492384/…
Lundin
4

Do padrão C (7.1.1 Definições de termos)

1 Uma string é uma sequência contígua de caracteres terminados por e incluindo o primeiro caractere nulo. Às vezes, o termo seqüência de caracteres multibyte é usado para enfatizar o processamento especial fornecido aos caracteres multibyte contidos na seqüência de caracteres ou para evitar confusão com uma sequência ampla. Um ponteiro para uma seqüência de caracteres é um ponteiro para seu caractere inicial (endereçado mais baixo). O comprimento de uma sequência é o número de bytes que precede o caractere nulo e o valor de uma sequência é a sequência dos valores dos caracteres contidos, em ordem.

Nesta declaração

char str [5] = "hello";

a string literal "hello"tem a representação interna como

{ 'h', 'e', 'l', 'l', 'o', '\0' }

portanto, possui 6 caracteres, incluindo o zero final. Seus elementos são usados ​​para inicializar a matriz de caracteres, strque reserva espaço apenas para 5 caracteres.

O padrão C (oposto ao padrão C ++) permite essa inicialização de uma matriz de caracteres quando o zero final de um literal de seqüência de caracteres não é usado como inicializador.

No entanto, como resultado, a matriz de caracteres strnão contém uma sequência.

Se você deseja que a matriz contenha uma string, você pode escrever

char str [6] = "hello";

ou apenas

char str [] = "hello";

No último caso, o tamanho da matriz de caracteres é determinado a partir do número de inicializadores da cadeia literal igual a 6.

Vlad de Moscou
fonte
0

Todas as cadeias de caracteres podem ser consideradas uma matriz de caracteres ( Sim ), todas as matrizes de caracteres podem ser consideradas cadeias de caracteres ( Não ).

Por que não? e por que isso importa?

Além das outras respostas que explicam que o comprimento de uma string não é armazenado em nenhum lugar como parte da string e nas referências ao padrão em que uma string é definida, o outro lado da página é "Como as funções da biblioteca C lidam com as strings?"

Enquanto uma matriz de caracteres pode conter os mesmos caracteres, é simplesmente uma matriz de caracteres, a menos que o último caractere seja seguido pelo caractere nulo-terminador . Esse caractere de terminação nula é o que permite que a matriz de caracteres seja considerada (manipulada como) uma string.

Todas as funções em C que esperam uma sequência como argumento esperam que a sequência de caracteres seja nula-terminada . Por quê?

Tem a ver com o modo como todas as funções de string funcionam. Como o comprimento não está incluído como parte de uma matriz, funções de string, avance na matriz até encontrar o caractere nulo (por exemplo, '\0'equivalente a decimal 0). Consulte Tabela e Descrição ASCII . Independentemente se você está usando strcpy, strchr, strcspn, etc .. Todas as funções de cadeia confiar na de terminação nul caráter estar presente para definir onde o fim dessa cadeia é.

Uma comparação de duas funções semelhantes de string.henfatizará a importância do caractere nul-terminator . Considere por exemplo:

    char *strcpy(char *dest, const char *src);

A strcpyfunção simplesmente copia bytes de srcaté destaté que o caractere de finalização nula seja encontrado informando strcpyonde parar a cópia de caracteres. Agora pegue a função semelhante memcpy:

    void *memcpy(void *dest, const void *src, size_t n);

A função executa uma operação semelhante, mas não considera ou requer que o srcparâmetro seja uma sequência. Como memcpynão é possível simplesmente avançar na srccópia de bytes destaté que um caractere de terminação nula seja atingido, é necessário um número explícito de bytes para copiar como um terceiro parâmetro. Esse terceiro parâmetro fornece memcpyo mesmo tamanho de informação strcpycapaz de derivar simplesmente avançando até encontrar um caractere de terminação nula .

(que também enfatiza o que está errado strcpy(ou qualquer função que esteja esperando uma string) se você não fornecer uma função com uma string terminada em nulo - ela não tem idéia de onde parar e terá prazer em correr pelo resto do segmento de memória invocando comportamento indefinido até que um caractere nulo seja encontrado em algum lugar da memória - ou ocorra uma falha de segmentação)

É por isso que as funções que esperam uma sequência terminada em nulo devem passar por uma sequência terminada em nulo e por que isso é importante .

David C. Rankin
fonte
0

Intuitivamente ...

Pense em uma matriz como uma variável (contém coisas) e uma sequência como um valor (pode ser colocada em uma variável).

Eles certamente não são a mesma coisa. No seu caso, a variável é muito pequena para conter a string, portanto a string é cortada. ("cadeias de caracteres entre aspas" em C têm um caractere nulo implícito no final.)

No entanto, é possível armazenar uma string em uma matriz muito maior que a string.

Observe que os operadores habituais de atribuição e comparação ( = == <etc.) não funcionam como o esperado. Mas a strxyzfamília de funções chega bem perto, quando você sabe o que está fazendo. Veja as perguntas frequentes C sobre strings e matrizes .

Artelius
fonte