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

506

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? Quero saber o que realmente acontece em termos de duração do armazenamento, tanto em tempo de compilação quanto em tempo de execução.

Contador de Histórias - Monica Sem Calúnia
fonte
8
char * s = "olá", aqui podemos apontar qualquer outra string em tempo de execução. Quero dizer que não é um ponteiro constante, é possível atribuir outro valor em tempo de execução p = "Nishant", enquanto s [] aqui s é um ponteiro constante. ..não pode ser redesignado outra string, mas podemos atribuir outro valor de caractere em s [index].
Nishant Kumar #

Respostas:

541

A diferença aqui é que

char *s = "Hello world";

será colocado "Hello world"nas partes somente leitura da memória e fazer sum ponteiro para isso torna ilegal qualquer operação de gravação nessa memória.

Enquanto estiver fazendo:

char s[] = "Hello world";

coloca a string literal na memória somente leitura e copia a string para a memória recém-alocada na pilha. Fazendo

s[0] = 'J';

legal.

Rickard
fonte
22
A cadeia literal "Hello world"está em "partes somente leitura da memória" nos dois exemplos. O exemplo com a matriz aponta para lá, o exemplo com a matriz copia os caracteres para os elementos da matriz.
Pmg #
28
pmg: no segundo caso, a cadeia literal não existe necessariamente na memória como um único objeto contíguo - é apenas um inicializador, o compilador pode emitir uma série de instruções de "carregamento imediato byte" que contêm os valores de caracteres incorporados eles.
CAF
10
O exemplo de matriz de caracteres não coloca necessariamente a string na pilha - se aparecer no nível do arquivo, provavelmente estará em algum tipo de segmento de dados inicializado.
CAF
9
Gostaria de salientar que char s = "xx" não precisa estar na memória somente leitura (algumas implementações não têm MMUs, por exemplo). O rascunho n1362 c1x simplesmente afirma que a modificação de uma matriz causa um comportamento indefinido. Mas +1 de qualquer maneira, já que confiar nesse comportamento é uma coisa boba de se fazer.
22420
3
Eu recebo uma compilação limpa em um arquivo que contém apenas char msg[] = "hello, world!"; a string e termina na seção de dados inicializados. Quando declarado char * constpara terminar na seção de dados somente leitura. gcc-4.5.3
gcbenison
152

Primeiro, nos argumentos das funções, 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. Para onde vai a corda 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 sintaxe da matriz aloca-a diretamente na nova memória. Assim, a modificação é segura:

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

No entanto, a matriz vive apenas enquanto seu escopo contaning, portanto, se você fizer isso em uma função, não retorne ou vaze um ponteiro para essa matriz - faça uma cópia em vez de com strdup()ou similar. Se a matriz estiver alocada no escopo global, é claro, não há problema.

bdonlan
fonte
72

Esta declaração:

char s[] = "hello";

Cria um objeto - uma charmatriz de tamanho 6, chamada s, inicializada com os valores 'h', 'e', 'l', 'l', 'o', '\0'. O local em que essa matriz está alocada na memória e por quanto tempo ela depende depende de onde a declaração aparece. Se a declaração estiver dentro de uma função, ela permanecerá até o final do bloco em que foi declarada e quase certamente será alocada na pilha; se estiver fora de uma função, provavelmente será armazenado em um "segmento de dados inicializado" carregado do arquivo executável na memória gravável quando o programa for executado.

Por outro lado, esta declaração:

char *s ="hello";

Cria dois objetos:

  • uma matriz somente leitura de 6 chars 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 ele permanece por toda a vida útil do programa); e
  • uma variável do tipo ponteiro para char, chamada s, que é inicializada com o local do primeiro caractere nessa matriz sem nome e somente leitura.

A matriz somente leitura sem nome geralmente está localizada no segmento "texto" do programa, o que significa que é carregada do disco na memória somente leitura, junto com o próprio código. A localização da svariável ponteiro na memória depende de onde a declaração aparece (como no primeiro exemplo).

caf
fonte
1
Em ambas as declarações para "olá", a memória é alocada em um momento múltiplo? = "olá" também armazenará primeiro a parte do segmento de texto e, durante o tempo de execução, será copiada na pilha, como Rickard afirmou na resposta. por favor, esclareça este ponto.
Nishant Kumar
2
@ Nishant: No char s[] = "hello"caso, o "hello"é apenas um inicializador dizendo ao compilador como a matriz deve ser inicializada. Pode ou não resultar em uma sequência correspondente no segmento de texto - por exemplo, se stiver duração de armazenamento estático, é provável que a única instância "hello"esteja no segmento de dados inicializado - o spróprio objeto . Mesmo que stenha duração de armazenamento automático, ele pode ser inicializado por uma sequência de armazenamentos literais, em vez de uma cópia (por exemplo movl $1819043176, -6(%ebp); movw $111, -2(%ebp)).
Caf
Mais precisamente, o GCC 4.8 o coloca no .rodataqual o script do vinculador despeja no mesmo segmento que .text. Veja minha resposta .
Ciro Santilli escreveu
@caf Na primeira resposta de Rickard, está escrito que char s[] = "Hello world";coloca a string literal na memória somente leitura e copia a string para a memória recém-alocada na pilha. Mas, a sua resposta só fala sobre o put string literal na memória só de leitura e ignora a segunda parte da frase que diz: copies the string to newly allocated memory on the stack. Então, sua resposta está incompleta por não especificar a segunda parte?
KPMG
1
@AjaySinghNegi: Como afirmei em outros comentários (a esta resposta e a resposta de Rickard), a string in char s[] = "Hellow world";é apenas um inicializador e não é necessariamente armazenada como uma cópia somente leitura separada. Se stiver duração de armazenamento estático, é provável que a única cópia da string esteja em um segmento de leitura e gravação no local de s, e mesmo se não houver, o compilador pode optar por inicializar a matriz com instruções de carregamento imediato ou similar, em vez de copiar de uma sequência de somente leitura. O ponto é que, nesse caso, a própria string do inicializador não tem presença de tempo de execução.
CAF
60

Dadas as declarações

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

assuma o seguinte mapa de memória hipotética:

                    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 literal de cadeia de caracteres "hello world"é uma matriz de 12 elementos char( const charem C ++) com duração de armazenamento estático, o que significa que a memória é alocada quando o programa é iniciado e permanece alocada até o término do programa. Tentar modificar o conteúdo de uma cadeia de caracteres literal invoca um comportamento indefinido.

A linha

char *s0 = "hello world";

define s0como um ponteiro para chara duração do armazenamento automático (ou seja, a variável s0existe apenas para o escopo em que é declarada) e copia o endereço da string literal ( 0x00008000neste exemplo) para ela. Note-se que desde que s0aponta para um literal de cadeia, ele não deve ser usado como um argumento para qualquer função que iria tentar modificá-lo (por exemplo, strtok(), strcat(), strcpy(), etc.).

A linha

char s1[] = "hello world";

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

s0e s1sã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 s0para apontar para uma literal de seqüência de caracteres diferente ou para outra variável. Você não pode reatribuir a variável s1para apontar para uma matriz diferente.

John Bode
fonte
2
Eu acho que o mapa de memória hipotético facilita a compreensão!
midnightBlue
32

C99 N1256 draft

Existem dois usos diferentes dos literais da cadeia de caracteres:

  1. Inicialize char[]:

    char c[] = "abc";      

    Isso é "mais mágico" e descrito em 6.7.8 / 14 "Inicialização":

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

    Portanto, este é apenas um atalho para:

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

    Como qualquer outra matriz regular, cpode ser modificado.

  2. Em qualquer outro lugar: gera um:

    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], também modifique __unnamed, que é UB.

    Isso está documentado em 6.4.5 "String literals":

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

    6 Não é especificado se essas matrizes são distintas, desde que seus elementos tenham os valores apropriados. Se o programa tentar modificar essa matriz, o comportamento será indefinido.

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

EXEMPLO 8: A declaração

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

define objetos de matriz de caracteres "simples" se tcujos 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 pcom o tipo "ponteiro para char" e inicializa-o para apontar para um objeto com o tipo "array of char" com comprimento 4 cujos elementos são inicializados com uma cadeia de caracteres literal. Se for feita uma tentativa pde modificar o conteúdo da matriz, o comportamento será indefinido.

Implementação do GCC 4.8 x86-64 ELF

Programa:

#include <stdio.h>

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

Compilar e descompilar:

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

A 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 o armazena char*em .rodataseção, não em .text.

Observe, no entanto, que o script vinculador padrão coloca .rodatae .textno mesmo segmento , que possui permissão de gravação, mas não possui permissã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

Se fizermos o mesmo para char[]:

 char s[] = "abc";

nós obtemos:

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

para que seja armazenado na pilha (em relação a %rbp).

Ciro Santilli adicionou uma nova foto
fonte
15
char s[] = "hello";

declara sser uma matriz charlonga o suficiente para armazenar o inicializador (5 + 1 chars) e inicializa a matriz copiando os membros da string especificada literalmente na matriz.

char *s = "hello";

declara sser um ponteiro para um ou mais (nesse caso, mais) se charaponta diretamente para um local fixo (somente leitura) que contém o literal "hello".

CB Bailey
fonte
1
Qual método é preferível usar nas funções se s não for alterado, f (const char s []) ou f (const char * s)?
Psiodelia
1
@psihodelia: Em uma declaração de função, não há diferença. Nos dois casos, sé um ponteiro para const char.
perfil completo de CB Bailey
4
char s[] = "Hello world";

Aqui sestá uma matriz de caracteres, que podem ser substituídos, se assim o desejarmos.

char *s = "hello";

Um literal de seqüência de caracteres é usado para criar esses blocos de caracteres em algum lugar da memória para o qual esse ponteiro sestá apontando. Podemos aqui reatribuir o objeto para o qual ele está apontando alterando isso, mas desde que aponte para uma string literal, o bloco de caracteres para o qual ele aponta não pode ser alterado.

Sailaja
fonte
@bo Persson Por que o bloco de caracteres não pode ser alterado no segundo caso?
Pankaj Mahato
3

Além disso, considere que, para fins de somente leitura, o uso de ambos é idêntico, você pode acessar um char indexando com []ou no *(<var> + <index>) formato:

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, ao tentar acessar a memória somente leitura.

Nick Louloudakis
fonte
Isso não é de forma alguma diferente do x[1] = 'a';que ocorrerá falha também (dependendo da plataforma, é claro).
glglgl
3

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 uma matriz '\0'será alocado como o elemento final.

Muzab
fonte
2
char *str = "Hello";

Os conjuntos acima str para apontar para o valor literal "Hello", que é codificado na imagem binária do programa, que é sinalizada como somente leitura na memória, significa que qualquer alteração nesse literal String é ilegal e causaria falhas de segmentação.

char str[] = "Hello";

copia a string para a memória 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, consulte a pergunta semelhante:

Por que recebo uma falha de segmentação ao escrever em uma string inicializada com "char * s", mas não "char s []"?

Mohit
fonte
0

No caso de:

char *x = "fred";

x é um valor l - ele pode ser atribuído. Mas no caso de:

char x[] = "fred";

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

Lee-Man
fonte
3
Tecnicamente, xé um valor não modificável. Em quase todos os contextos, porém, ele será avaliado como um ponteiro para seu primeiro elemento, e esse valor é um rvalor.
CAF
0
char *s1 = "Hello world"; // Points to fixed character string which is not allowed to modify
char s2[] = "Hello world"; // As good as fixed array of characters in string so allowed to modify

// s1[0] = 'J'; // Illegal
s2[0] = 'J'; // Legal
Atul
fonte
-1

À luz dos comentários aqui, deve ser óbvio que: char * s = "olá"; É uma péssima idéia e deve ser usada em escopo muito restrito.

Essa pode ser uma boa oportunidade para apontar que a "correção constante" é 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 os ponteiros entram em ação.

Chega de melodrama, eis o que se pode obter ao decorar ponteiros com "const". (Nota: É necessário ler as declarações dos ponteiros da direita para a esquerda.) Aqui estão as três maneiras diferentes de se proteger ao brincar com os ponteiros:

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

- ou seja, o objeto DBJ não pode ser alterado via p.

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

- ou seja, você pode alterar o objeto DBJ via p, mas não pode alterar o ponteiro p.

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

- ou seja, você não pode alterar o ponteiro p, nem o objeto DBJ via p.

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

(Suponha que você esteja usando o compilador C ++, é claro?)

--DBJ


fonte
Está tudo correto, mas não tem nada a ver com a pergunta. E, quanto à sua suposição sobre um compilador C ++, a questão é marcada como C, não como C ++.
Fabio diz Reinstate Monica
Não há nada de ruim em char * s = "const string";
Paul Smith