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

StackOverflow https://stackoverflow.com/questions/164194

  •  03-07-2019
  •  | 
  •  

Pergunta

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

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

Embora isso funcione perfeitamente bem:

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

Testado com MSVC e GCC.

Foi útil?

Solução

Veja 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 trava se eu tentar atribuir um novo valor a p[i].

A:Um string literal (o termo formal para uma string de dupla citação na fonte C) pode ser usada de duas maneiras ligeiramente diferentes:

  1. Como inicializador de um array de char, 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 que portanto, não pode ser necessariamente modificado.Em um contexto de expressão, a matriz é convertida de uma só vez em um ponteiro, como de costume (ver secção 6), por isso a segunda declaração inicializa p para apontar para o primeiro array sem nome elemento.

Alguns compiladores têm um switch controlando se os literais de strings 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 strings sejam formalmente tratados como matrizes de const char (para obter melhores erros).

Outras dicas

Normalmente, os literais de string são armazenados na memória somente leitura quando o programa é executado. Isso é para impedir que você altere acidentalmente uma string constante. Em seu primeiro exemplo, "string" é armazenado na memória somente leitura e *str aponta para o primeiro personagem. O segfault acontece quando você tenta mudar o primeiro personagem para 'z'.

No segundo exemplo, a string "string" é copiado pelo compilador de sua casa somente leitura para o str[] variedade. Em seguida, alterando o primeiro caractere é permitido. Você pode verificar isso imprimindo o endereço de cada um:

printf("%p", str);

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

printf("%d", sizeof(str));

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 local da memória onde as instruções são carregadas.Isso é somente leitura por motivos óbvios, 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 isso, kaboom.Segfault.

Quando escrito como uma matriz, o compilador coloca os dados da string inicializada no segmento de dados, que é o mesmo local onde ficam suas variáveis ​​globais e outras.Esta memória é mutável, pois não existem instruções no segmento de dados.Desta vez, quando o compilador inicializa a matriz de caracteres (que ainda é apenas um char*), ele aponta para o segmento de dados em vez do segmento de texto, que você pode alterar com segurança em tempo de execução.

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

C99 N1256 Draft

Existem dois usos diferentes dos literais de caracteres de caracteres:

  1. Inicializar 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 string de caractere literal, opcionalmente fechada em aparelhos. Caracteres sucessivos do caractere literal (incluindo o caractere nulo terminando se houver espaço ou se a matriz for de tamanho desconhecido) inicialize os elementos da matriz.

    Então este é apenas um atalho para:

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

    Como qualquer outra matriz regular, c pode 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 o elenco implícito de char[] para char *, o que é sempre legal.

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

    Isso está documentado em 6.4.5 "String literais":

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

    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 "simples" de matriz de char s e t cujos elementos são inicializados com literais de caracteres.

Esta declaração é idêntica a

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 o tipo "ponteiro para char" e inicializa para apontar para um objeto com tipo "matriz de char" com o comprimento 4 cujos elementos são inicializados com uma corda de caractere literal. Se uma tentativa for feita para usar p Para modificar o conteúdo da matriz, o comportamento é indefinido.

GCC 4.8 X86-64 ELF Implementação

Programa:

#include <stdio.h>

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

Compilar e decompilar:

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: lojas GCC char* em .rodata seçã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)

Então é armazenado na pilha (em relação a %rbp).

Observe, no entanto, que o script de ligação padrão coloca .rodata e .text No mesmo segmento, que foi executado, mas sem 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

No primeiro código, "String" é uma constante de string, e as constantes de string nunca devem ser modificadas, pois geralmente são colocadas na memória de leitura. "STR" é um ponteiro usado para modificar a constante.

No segundo código, "String" é um inicializador de matriz, 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.

Porque o tipo de "whatever" No contexto do primeiro exemplo, é const char * (Mesmo se você atribui-lo a um char não consultoso*), o que significa que você não deve tentar escrever para ele.

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

Para entender esse erro ou problema, você deve primeiro saber a diferença b/w o ponteiro e a matriz, então aqui primeiro eu explico as diferenças por eles por eles

Array da string

 char strarray[] = "hello";

Na matriz de memória é armazenado em células de memória contínuas, armazenadas como [h][e][l][l][o][\0] =>[] IS 1 CHAR BYTE Tamanho da célula de memória, e essas células de memória contínuas podem ser acessadas pelo nome chamado Strarray aqui.So aqui String Array strarray por si só contendo todos os caracteres da string inicializados. Nesse caso, aqui "hello"Portanto, podemos alterar facilmente seu conteúdo de memória acessando cada caractere pelo seu valor de índice

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

e seu valor mudou para 'm' Portanto, o valor de Strarray mudou para "mello";

Um ponto a ser observado aqui que podemos alterar o conteúdo da matriz de strings alterando o personagem por personagem, mas não podemos inicializar outra string diretamente strarray="new string" é inválido

Ponteiro

Como todos sabemos que o ponteiro aponta para a localização da memória na memória, o ponteiro indiializado aponta para a localização da memória aleatória, e após a inicialização aponta para um local de memória específico

char *ptr = "hello";

Aqui o ponteiro PTR é inicializado para cordas "hello" que é string constante armazenada na memória de leitura (ROM) então "hello" não pode ser alterado como é armazenado em ROM

e PTR é armazenado na seção de pilha e apontando para string constante "hello"

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

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

ptr="new string"; is valid
char *str = "string";  

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

Então str[0]= está tentando escrever no código somente leitura do aplicativo. Eu acho que isso provavelmente depende do compilador.

char *str = "string";

aloca um ponteiro para um literal de corda, 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

O C FAQ que @Matli vinculou-se a mencionar, mas ninguém mais aqui ainda o fez, então para esclarecimentos: se uma string literal (string dupla em sua fonte) é usada em qualquer lugar outro que não seja Para inicializar uma matriz de caracteres (ou seja: @segundo exemplo de Mark, que funciona corretamente), essa string é armazenada pelo compilador em um especial Tabela de corda estática, que é semelhante à criação de uma variável estática global (somente leitura, é claro) que é essencialmente anônima (não possui "nome" variável). o somente leitura A parte é a parte importante e é por isso que o primeiro exemplo de código do @Mark segfaults.

o

 char *str = "string";

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

  str[0] = 'z';

você tem uma falha de seg. Em algumas plataformas, o literal pode estar em 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 variedade de personagens e cópias A sequência literal nessa matriz, que é totalmente gravável, portanto a atualização subsequente não é problema.

Literais de string como "String" provavelmente são alocados no espaço de endereço do seu executável como dados somente leitura (dê ou pegue seu compilador). Quando você vai tocá -lo, enlouquece que você está na área de maiô e permite que você saiba com uma falha no 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.

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

Em primeiro lugar, str é um ponteiro que aponta para "string". O compilador pode colocar literais de cordas em locais na memória para os quais você não pode escrever, mas só pode ler. (Isso realmente deveria ter desencadeado um aviso, já que você está atribuindo um const char * para um char *. Você teve avisos desativados ou acabou de ignorá -los?)

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

Suponha que as cordas 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' entra em escopo. Aqui 'A' é uma matriz definida na pilha. Isso significa que a string será criada na pilha e seus dados são copiados da memória Code (Text), que normalmente é somente leitura (isso é específico da implementação, um compilador pode colocar esses dados do programa somente leitura na memória de leitura-gravidez também ).

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

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

A falha de segmentação é causada quando você acessa a memória que não é acessível.

char *str é um ponteiro para uma string que não é modificável (o motivo da falha da SEG).

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top