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

287

O código a seguir recebe a falha seg na linha 2:

char *str = "string";
str[0] = 'z';  // could be also written as *str = 'z'
printf("%s\n", str);

Enquanto isso funciona perfeitamente bem:

char str[] = "string";
str[0] = 'z';
printf("%s\n", str);

Testado com MSVC e GCC.

Markus
fonte
1
É engraçado - mas isso realmente compila e funciona perfeitamente ao usar o compilador do Windows (cl) em um prompt de comando do desenvolvedor do visual studio. Got me confundido por alguns momentos ...
David Refaeli

Respostas:

241

Consulte as Perguntas frequentes sobre C, pergunta 1.32

P : Qual é a diferença entre essas inicializações?
char a[] = "string literal";
char *p = "string literal";
Meu programa falha se eu tentar atribuir um novo valor a p[i].

R : Um literal de cadeia (o termo formal para uma cadeia de aspas duplas na fonte C) pode ser usado de duas maneiras ligeiramente diferentes:

  1. Como inicializador para uma matriz de caracteres, como na declaração de char a[], especifica os valores iniciais dos caracteres nessa matriz (e, se necessário, seu tamanho).
  2. Em qualquer outro lugar, ele se transforma em uma matriz estática e sem nome de caracteres, e essa matriz sem nome pode ser armazenada na memória somente leitura e, portanto, não pode ser necessariamente modificada. Em um contexto de expressão, a matriz é convertida imediatamente em um ponteiro, como de costume (consulte a seção 6), de modo que a segunda declaração inicializa p para apontar para o primeiro elemento da matriz não nomeada.

Alguns compiladores têm uma opção que controla se os literais de string são graváveis ​​ou não (para compilar código antigo), e alguns podem ter opções para fazer com que os literais de string sejam tratados formalmente como matrizes de const char (para melhor captura de erros).

matli
fonte
7
Alguns outros pontos: (1) o segfault ocorre como descrito, mas sua ocorrência é uma função do ambiente de execução; se o mesmo código estava em um sistema incorporado, a gravação pode não ter efeito ou pode realmente alterar os s para z. (2) Como os literais de string não são graváveis, o compilador pode economizar espaço colocando duas instâncias de "string" no mesmo local; ou, se em outro lugar do código você tiver "outra string", um pedaço de memória poderá suportar os dois literais. Claramente, se fosse permitido ao código alterar esses bytes, erros estranhos e difíceis poderiam ocorrer.
Greggo
1
@greggo: Bom ponto. Também existe uma maneira de fazer isso em sistemas com MMU usando mprotectpara acenar a proteção somente leitura (veja aqui ).
Então char * p = "blah" realmente cria uma matriz temporária? Estranho.
Rahul tyagi 03/12/2014
1
E depois de 2 anos escrevendo em C ++ ... TIL
zeboidlund
@rahultyagi, o que você quer dizer?
precisa
105

Normalmente, literais de seqüência de caracteres são armazenados na memória somente leitura quando o programa é executado. Isso evita que você altere acidentalmente uma constante de string. No seu primeiro exemplo, "string"é armazenado na memória somente leitura e *straponta para o primeiro caractere. O segfault acontece quando você tenta alterar o primeiro caractere para 'z'.

No segundo exemplo, a seqüência de caracteres "string"é copiada pelo compilador de sua casa somente leitura para a str[]matriz. É permitido alterar o primeiro caractere. Você pode verificar isso imprimindo o endereço de cada um:

printf("%p", str);

Além disso, imprimir o tamanho de strno segundo exemplo mostrará que o compilador alocou 7 bytes para ele:

printf("%d", sizeof(str));
Greg Hewgill
fonte
13
Sempre que usar "% p" em printf, você deve lançar o ponteiro para void * como em printf ("% p", (void *) str); Ao imprimir um size_t com printf, você deve usar "% zu" se estiver usando o padrão C mais recente (C99).
317 Chris Young
4
Além disso, os parênteses com sizeof são necessários apenas quando se assume o tamanho de um tipo (o argumento se parece com uma conversão). Lembre-se de que sizeof é um operador, não uma função.
descontraia
34

A maioria dessas respostas está correta, mas apenas para adicionar um pouco mais de clareza ...

A "memória somente leitura" a que as pessoas estão se referindo é o segmento de texto em termos ASM. É o mesmo lugar na memória em que as instruções são carregadas. Isso é somente leitura por razões óbvias, como segurança. Quando você cria um char * inicializado em uma string, os dados da string são compilados no segmento de texto e o programa inicializa o ponteiro para apontar para o segmento de texto. Então, se você tentar mudar, kaboom. Segfault.

Quando gravado como uma matriz, o compilador coloca os dados da string inicializada no segmento de dados, que é o mesmo local em que vivem suas variáveis ​​globais. Essa memória é mutável, pois não há instruções no segmento de dados. Desta vez, quando o compilador inicializa a matriz de caracteres (que ainda é apenas um caractere *), está apontando para o segmento de dados em vez do segmento de texto, que você pode alterar com segurança no tempo de execução.

Bob Somers
fonte
Mas não é verdade que pode haver implementações que permitam modificar a "memória somente leitura"?
Pacerier 21/09
Quando gravado como uma matriz, o compilador coloca os dados da string inicializada no segmento de dados, se forem estáticos ou globais. Caso contrário (por exemplo, para uma matriz automática normal), ela é colocada na pilha, no quadro da pilha da função principal. Corrigir?
SE
26

Por que recebo uma falha de segmentação ao escrever em uma string?

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 o inicializa para apontar para um objeto com o tipo "array of char" com comprimento 4 cujos elementos são inicializados com uma literal de cadeia de caracteres. 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.

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).

Observe, no entanto, que o script do vinculador padrão coloca .rodatae .textno mesmo segmento, que tem execuçã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
Ciro Santilli adicionou uma nova foto
fonte
17

No primeiro código, "string" é uma constante de string, e as constantes de string nunca devem ser modificadas porque são frequentemente colocadas na memória somente leitura. "str" ​​é um ponteiro usado para modificar a constante.

No segundo código, "string" é um inicializador de array, uma espécie de mão curta para

char str[7] =  { 's', 't', 'r', 'i', 'n', 'g', '\0' };

"str" ​​é uma matriz alocada na pilha e pode ser modificada livremente.

Andru Luvisi
fonte
1
Na pilha ou no segmento de dados, se stré global ou static.
Gauthier
12

Porque o tipo de "whatever"no contexto do 1º exemplo é const char *(mesmo que você o atribua a um caractere não-const *), o que significa que você não deve tentar escrever nele.

O compilador aplicou isso colocando a string em uma parte de memória somente leitura, portanto, a gravação nela gera um segfault.


fonte
8

Para entender esse erro ou problema, você deve primeiro saber a diferença entre o ponteiro e o array, então aqui primeiro eu explico as diferenças entre eles

matriz de string

 char strarray[] = "hello";

Na memória matriz é armazenada em células de memória contínuos, armazenado como [h][e][l][l][o][\0] =>[]é célula de memória tamanho byte um carvão animal, e esta células de memória contínuos pode ser acesso por nome com o nome strArray here.so aqui matriz de cadeia strarraysi contendo todos os caracteres de cadeia inicializado para este it.In caso aqui, "hello" para que possamos alterar facilmente seu conteúdo de memória, acessando cada caractere por seu valor de índice

`strarray[0]='m'` it access character at index 0 which is 'h'in strarray

e seu valor foi alterado para 'm'um valor tão restrito alterado para "mello";

Um ponto a ser observado aqui é que podemos alterar o conteúdo da matriz de string alterando caractere por caractere, mas não podemos inicializar outra string diretamente para ela, como strarray="new string"inválida.

Ponteiro

Como todos sabemos, o ponteiro aponta para o local da memória na memória, o ponteiro não inicializado aponta para o local aleatório da memória e, após a inicialização, aponta para o local específico da memória

char *ptr = "hello";

aqui o ponteiro ptr é inicializado para uma string "hello"que é uma string constante armazenada na memória somente leitura (ROM), portanto "hello"não pode ser alterada, pois é armazenada na ROM

e ptr é armazenado na seção da pilha e apontando para a string constante "hello"

então ptr [0] = 'm' é inválido, pois você não pode acessar a memória somente leitura

Mas ptr pode ser inicializado para outro valor de string diretamente, pois é apenas um ponteiro, para que possa ser apontado para qualquer endereço de memória da variável do seu tipo de dados

ptr="new string"; is valid
Comunidade
fonte
7
char *str = "string";  

Os itens acima strapontam para o valor literal "string"codificado na imagem binária do programa, que provavelmente é sinalizada como somente leitura na memória.

Assim, str[0]=está tentando gravar no código somente leitura do aplicativo. Eu acho que isso provavelmente é dependente do compilador.

DougN
fonte
6
char *str = "string";

aloca um ponteiro para uma string literal, que o compilador está colocando em uma parte não modificável do seu executável;

char str[] = "string";

aloca e inicializa uma matriz local que é modificável

Rob Walker
fonte
podemos escrever int *b = {1,2,3) como escrevemos char *s = "HelloWorld"?
Suraj Jain
6

O FAQ C que o @matli vinculou menciona, mas mais ninguém aqui o fez, então, para esclarecimentos: se uma literal de string (string com aspas duplas na sua fonte) for usada em outro lugar que não seja para inicializar uma matriz de caracteres (por exemplo: @ O segundo exemplo de Mark, que funciona corretamente), que a string é armazenada pelo compilador em uma tabela estática especial , semelhante à criação de uma variável estática global (somente leitura, é claro) que é essencialmente anônima (não tem nome "variável" "). A parte somente leitura é a parte importante e é por isso que o primeiro exemplo de código do @ Mark é segmentado.

rpj
fonte
podemos escrever int *b = {1,2,3) como escrevemos char *s = "HelloWorld"?
Suraj Jain
4

o

 char *str = "string";

A linha define um ponteiro e o aponta para uma string literal. A cadeia literal não é gravável, portanto, quando você faz:

  str[0] = 'z';

você recebe uma falha seg. Em algumas plataformas, o literal pode estar na memória gravável para que você não veja um segfault, mas é um código inválido (resultando em comportamento indefinido) independentemente.

A linha:

char str[] = "string";

aloca uma matriz de caracteres e copia a string literal para essa matriz, que é totalmente gravável, para que a atualização subsequente não seja um problema.

Michael Burr
fonte
podemos escrever int *b = {1,2,3) como escrevemos char *s = "HelloWorld"?
Suraj Jain
3

Literais de string como "string" provavelmente são alocados no espaço de endereço do executável como dados somente leitura (fornecer ou receber seu compilador). Quando você vai tocá-lo, fica assustado que você esteja na área de roupas de banho e avisa com uma falha de seg.

No seu primeiro exemplo, você está recebendo um ponteiro para esses dados const. No seu segundo exemplo, você está inicializando uma matriz de 7 caracteres com uma cópia dos dados const.

Jurney
fonte
2
// create a string constant like this - will be read only
char *str_p;
str_p = "String constant";

// create an array of characters like this 
char *arr_p;
char arr[] = "String in an array";
arr_p = &arr[0];

// now we try to change a character in the array first, this will work
*arr_p = 'E';

// lets try to change the first character of the string contant
*str_p = 'G'; // this will result in a segmentation fault. Comment it out to work.


/*-----------------------------------------------------------------------------
 *  String constants can't be modified. A segmentation fault is the result,
 *  because most operating systems will not allow a write
 *  operation on read only memory.
 *-----------------------------------------------------------------------------*/

//print both strings to see if they have changed
printf("%s\n", str_p); //print the string without a variable
printf("%s\n", arr_p); //print the string, which is in an array. 
jokeysmurf
fonte
1

Em primeiro lugar, stré um ponteiro que aponta para "string". É permitido ao compilador colocar literais de seqüência de caracteres em lugares na memória em que você não pode gravar, mas pode apenas ler. (Isso realmente deveria ter acionado um aviso, já que você está atribuindo const char *a a char *. Você tinha avisos desativados ou apenas os ignorou?)

Em segundo lugar, você está criando uma matriz, que é a memória à qual você tem acesso total e inicializando-a "string". Você está criando um char[7](seis para as letras, um para o final '\ 0') e faz o que quiser com ele.

David Thornley
fonte
@Ferruccio,? O constprefixo Yes torna variáveis ​​somente
leitura
Em literais de seqüência de caracteres C char [N], o tipo não é const char [N], portanto, não há aviso. (Você pode mudar isso no gcc, pelo menos pela passagem -Wwrite-strings.)
melpomene
0

Suponha que as strings sejam,

char a[] = "string literal copied to stack";
char *p  = "string literal referenced by p";

No primeiro caso, o literal deve ser copiado quando 'a' entrar no escopo. Aqui 'a' é uma matriz definida na pilha. Isso significa que a string será criada na pilha e seus dados serão copiados da memória de código (texto), que geralmente é somente leitura (isso é específico da implementação, um compilador pode colocar esses dados do programa somente leitura na memória gravável também )

No segundo caso, p é um ponteiro definido na pilha (escopo local) e referente a uma string literal (dados ou texto do programa) armazenada em outro local. Normalmente, modificar essa memória não é uma boa prática nem incentivada.

Venki
fonte
-1

Primeiro é uma string constante que não pode ser modificada. O segundo é uma matriz com valor inicializado, para que possa ser modificada.

libralhb
fonte
-2

A falha de segmentação é causada quando você tenta acessar a memória inacessível.

char *str é um ponteiro para uma string que não é modificável (o motivo da obtenção do segfault).

Considerando que char str[]é uma matriz e pode ser modificável.

Raghu Srikanth Reddy
fonte