Qual é a diferença entre char s e char * s?

Em C, pode-se usar uma string literal em uma declaração como esta:

char s[] = "hello"; 

ou assim:

 char *s = "hello"; 

Então qual é a diferença? Eu quero saber o que realmente acontece em termos de duração de armazenamento, tanto em tempo de compilation quanto em tempo de execução.

A diferença aqui é que

 char *s = "Hello world"; 

colocará "Hello world" nas partes somente leitura da memory , e fazendo um ponteiro para isso faz com que qualquer operação de escrita nessa memory seja ilegal.

Enquanto fazendo:

 char s[] = "Hello world"; 

coloca a string literal em memory somente leitura e copia a string para a memory recém alocada na pilha. Assim fazendo

 s[0] = 'J'; 

legal.

Em primeiro lugar, em argumentos de function, eles são exatamente equivalentes:

 void foo(char *x); void foo(char x[]); // exactly the same in all respects 

Em outros contextos, char * aloca um ponteiro, enquanto char [] aloca uma matriz. Onde a string vai no primeiro caso, você pergunta? O compilador aloca secretamente uma matriz anônima estática para manter a string literal. Assim:

 char *x = "Foo"; // is approximately equivalent to: static const char __secret_anonymous_array[] = "Foo"; char *x = (char *) __secret_anonymous_array; 

Observe que você nunca deve tentar modificar o conteúdo dessa matriz anônima por meio desse ponteiro; os efeitos são indefinidos (geralmente significando uma falha):

 x[1] = 'O'; // BAD. DON'T DO THIS. 

O uso da syntax de array aloca-a diretamente em uma nova memory. Assim, a modificação é segura:

 char x[] = "Foo"; x[1] = 'O'; // No problem. 

No entanto, o array só tem a duração de seu escopo de contenção, portanto, se você fizer isso em uma function, não retorne nem vaze um ponteiro para esse array – faça uma cópia com strdup() ou similar. Se a matriz é alocada no escopo global, é claro, não há problema.

Esta declaração:

 char s[] = "hello"; 

Cria um object – uma matriz char de tamanho 6, chamada s , inicializada com os valores 'h', 'e', 'l', 'l', 'o', '\0' . Onde essa matriz é alocada na memory e por quanto tempo ela depende, depende de onde a declaração aparece. Se a declaração estiver dentro de uma function, ela viverá até o final do bloco em que está declarada e quase certamente será alocada na pilha; se estiver fora de uma function, ela provavelmente será armazenada em um “segmento de dados inicializados” carregado do arquivo executável na memory gravável quando o programa for executado.

Por outro lado, esta declaração:

 char *s ="hello"; 

Cria dois objects:

  • uma matriz somente leitura de 6 char contendo os valores 'h', 'e', 'l', 'l', 'o', '\0' , que não tem nome e tem duração de armazenamento estático (o que significa que vive durante toda a vida do programa); e
  • uma variável do tipo pointer-to-char, chamada s , que é inicializada com a localização do primeiro caractere nessa matriz unnamed e somente leitura.

A matriz somente leitura sem nome geralmente está localizada no segmento “texto” do programa, o que significa que ela é carregada do disco para a memory somente leitura, junto com o próprio código. O local da variável s pointer na memory depende de onde a declaração aparece (assim como no primeiro exemplo).

Dadas as declarações

 char *s0 = "hello world"; char s1[] = "hello world"; 

suponha o seguinte mapa de memory hipotético:

                     0x01 0x02 0x03 0x04
         0x00008000: 'h' e '' l '' l '
         0x00008004: 'o' '' 'w' 'o'
         0x00008008: 'r' 'l' d '0x00
         ...
 s0: 0x00010000: 0x00 0x00 0x80 0x00
 s1: 0x00010004: 'h' e '' l '' l '
         0x00010008: 'o' '' 'w' 'o'
         0x0001000C: 'r' 'l' d '0x00

A string literal "hello world" é uma matriz de 12 elementos de char ( const char em C ++) com duração de armazenamento estático, o que significa que a memory para ela é alocada quando o programa é iniciado e permanece alocada até que o programa termine. A tentativa de modificar o conteúdo de um literal de string chama o comportamento indefinido.

A linha

 char *s0 = "hello world"; 

define s0 como um ponteiro para char com duração de armazenamento automático (significando que a variável s0 existe somente para o escopo no qual ela é declarada) e copia o endereço da string literal ( 0x00008000 neste exemplo) para ela. Note que, como s0 aponta para uma string literal, ela não deve ser usada como um argumento para qualquer function que tente modificá-lo (por exemplo, strtok() , strcat() , strcpy() , etc.).

A linha

 char s1[] = "hello world"; 

define s1 como um array de 12 elementos de char (o comprimento é obtido a partir do literal de string) com duração de armazenamento automático e copia o conteúdo do literal para o array. Como você pode ver no mapa de memory, temos duas cópias da string "hello world" ; A diferença é que você pode modificar a string contida em s1 .

s0 e s1 são intercambiáveis ​​na maioria dos contextos; aqui estão as exceções:

 sizeof s0 == sizeof (char*) sizeof s1 == 12 type of &s0 == char ** type of &s1 == char (*)[12] // pointer to a 12-element array of char 

Você pode reatribuir a variável s0 para apontar para um literal de string diferente ou para outra variável. Você não pode reatribuir a variável s1 para apontar para uma matriz diferente.

C99 N1256 draft

Existem dois usos completamente diferentes de literais de array:

  1. Inicializar char[] :

     char c[] = "abc"; 

    Isso é “mais mágica” e descrito em 6.7.8 / 14 “Inicialização” :

    Uma matriz de tipo de caractere pode ser inicializada por um literal de cadeia de caracteres, opcionalmente entre chaves. Caracteres sucessivos do literal da cadeia de caracteres (incluindo o caractere nulo de terminação, se houver espaço ou se o array for de tamanho desconhecido) inicializam os elementos do array.

    Então, isso é apenas um atalho para:

     char c[] = {'a', 'b', 'c', '\0'}; 

    Como qualquer outro array regular, c pode ser modificado.

  2. Em toda parte: gera um:

    • sem nome
    • array de char Qual é o tipo de literais de string em C e C ++?
    • com armazenamento estático
    • que dá UB se modificado

    Então, quando você escreve:

     char *c = "abc"; 

    Isso é semelhante a:

     /* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed; 

    Observe a conversão implícita de char[] para char * , que é sempre legal.

    Então, se você modificar c[0] , você também modifica __unnamed , que é UB.

    Isto está documentado em 6.4.5 “Literais de string” :

    5 Na fase de tradução 7, um byte ou código de valor zero é anexado a cada seqüência de caracteres multibyte que resulta de uma literal de cadeia de caracteres ou literais. A sequência de caracteres multibyte é então usada para inicializar uma matriz de duração e comprimento de armazenamento estáticos apenas o suficiente para conter a sequência. Para literais de cadeia de caracteres, os elementos da matriz têm o tipo char e são inicializados com os bytes individuais da seqüência de caracteres multibyte […]

    6 Não é especificado se esses arrays são distintos, desde que seus elementos tenham os valores apropriados. Se o programa tentar modificar essa matriz, o comportamento é indefinido.

6.7.8 / 32 “Inicialização” dá um exemplo direto:

EXEMPLO 8: A declaração

 char s[] = "abc", t[3] = "abc"; 

define objects de matriz char “simples” s e t cujos elementos são inicializados com literais de cadeia de caracteres.

Esta declaração é idêntica à

 char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' }; 

O conteúdo das matrizes é modificável. Por outro lado, a declaração

 char *p = "abc"; 

define p com tipo “pointer to char” e inicializa para apontar para um object com o tipo “array de char” com comprimento 4 cujos elementos são inicializados com um literal de string de caractere. Se for feita uma tentativa de usar p para modificar o conteúdo da matriz, o comportamento é indefinido.

Implementação do GCC 4.8 x86-64 ELF

Programa:

 #include  int main() { char *s = "abc"; printf("%s\n", s); return 0; } 

Compile e descompile:

 gcc -ggdb -std=c99 -c main.c objdump -Sr main.o 

Saída contém:

  char *s = "abc"; 8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp) f: 00 c: R_X86_64_32S .rodata 

Conclusão: O GCC armazena o char* it na seção .rodata , não no .text .

Se fizermos o mesmo para o char[] :

  char s[] = "abc"; 

nós obtemos:

 17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp) 

então fica armazenado na pilha (em relação a %rbp ).

No entanto, observe que o script de vinculador padrão coloca .rodata e .text no mesmo segmento, que tem permissão de execução, mas não de gravação. Isso pode ser observado com:

 readelf -l a.out 

que contém:

  Section to Segment mapping: Segment Sections... 02 .text .rodata 
 char s[] = "hello"; 

declara s como uma matriz de char que é longa o suficiente para manter o inicializador (5 + 1 char s) e inicializa a matriz copiando os membros da cadeia de caracteres dada literal na matriz.

 char *s = "hello"; 

declara s ser um ponteiro para um ou mais (neste caso mais) char s e aponta diretamente para um local fixo (somente leitura) contendo o literal "hello" .

 char s[] = "Hello world"; 

Aqui, s é uma matriz de caracteres, que pode ser sobrescrita, se desejarmos.

 char *s = "hello"; 

Um literal de string é usado para criar esses blocos de caracteres em algum lugar na memory para a qual esse ponteiro está apontando. Podemos aqui reatribuir o object para o qual ele está apontando, alterando isso, mas, desde que ele aponte para uma string literal, o bloco de caracteres para o qual ele aponta não pode ser alterado.

Como acréscimo, considere que, como para propósitos somente de leitura, o uso de ambos é idêntico, você pode acessar um char indexando com o formato [] ou *( + ) :

 printf("%c", x[1]); //Prints r 

E:

 printf("%c", *(x + 1)); //Prints r 

Obviamente, se você tentar fazer

 *(x + 1) = 'a'; 

Você provavelmente receberá uma Falha de Segmentação, pois está tentando acessar a memory somente leitura.

Apenas para adicionar: você também obtém valores diferentes para seus tamanhos.

 printf("sizeof s[] = %zu\n", sizeof(s)); //6 printf("sizeof *s = %zu\n", sizeof(s)); //4 or 8 

Como mencionado acima, para um array '\0' será alocado como o elemento final.

 char *str = "Hello"; 

O acima define str para apontar para o valor literal “Olá”, que é codificado na imagem binária do programa, que é sinalizado como somente leitura na memory, significa que qualquer alteração neste literal String é ilegal e que lançaria falhas de segmentação.

 char str[] = "Hello"; 

copia a string para a memory recém alocada na pilha. Assim, qualquer alteração é permitida e legal.

 means str[0] = 'M'; 

vai mudar o str para “Mello”.

Para mais detalhes, por favor, faça a pergunta semelhante:

Por que recebo uma falha de segmentação ao gravar em uma cadeia inicializada com “char * s”, mas não “char s []”?

No caso de:

 char *x = "fred"; 

x é um lvalue – pode ser atribuído a. Mas no caso de:

 char x[] = "fred"; 

x não é um lvalue, é um rvalue – você não pode atribuir a ele.

À luz dos comentários aqui, deve ser óbvio que: char * s = “olá”; É uma má ideia e deve ser usada em um escopo muito restrito.

Esta pode ser uma boa oportunidade para salientar que “const correctness” é uma “coisa boa”. Sempre e onde você puder, use a palavra-chave “const” para proteger seu código, de chamadores ou programadores “relaxados”, que geralmente são mais “relaxados” quando pointers entram em ação.

Chega de melodrama, aqui está o que se pode conseguir ao adorar pointers com “const”. (Nota: é preciso ler as declarações de ponteiro da direita para a esquerda.) Aqui estão as 3 maneiras diferentes de se proteger quando estiver jogando com pointers:

 const DBJ* p means "p points to a DBJ that is const" 

– isto é, o object DBJ não pode ser alterado via p.

 DBJ* const p means "p is a const pointer to a DBJ" 

– isto é, você pode mudar o object DBJ via p, mas você não pode mudar o próprio ponteiro p.

 const DBJ* const p means "p is a const pointer to a const DBJ" 

– isto é, você não pode mudar o ponteiro p em si, nem pode mudar o object DBJ via p.

Os erros relacionados à tentativa de mutação de const-ant são capturados em tempo de compilation. Não há espaço de tempo de execução ou penalidade de velocidade para const.

(Suposição é você está usando o compilador de C ++, naturalmente?)

– DBJ