Dlaczego pojawia się błąd segmentacji podczas pisania do łańcucha inicjalizowanego "char *s", ale nie "char s[]"?

Następujący kod otrzymuje błąd seg w linii 2:

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

Podczas gdy to działa doskonale:

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

Testowane z MSVC i GCC.

Author: Antti Haapala, 2008-10-02

16 answers

Zobacz FAQ C, pytanie 1.32

Q: Jaka jest różnica między tymi inicjalizacjami?
char a[] = "string literal";
char *p = "string literal";
Mój program zawiesza się, jeśli próbuję przypisać nową wartość p[i].

A : literał Łańcuchowy (termin formalny dla podwójnego cytowanego ciągu w C źródło) może być stosowany w dwóch nieznacznie różne sposoby:

  1. jako inicjalizator tablicy znaków , tak jak w deklaracji char a[], określa wartości początkowe znaków w tej tablicy (oraz, w razie potrzeby jego wielkość).
  2. gdziekolwiek indziej, zamienia się w nienazwaną, statyczną tablicę znaków, i ta nienazwana tablica może być przechowywana w pamięci tylko do odczytu, a które dlatego nie musi być zmodyfikowany. W kontekście wyrażenia, tablica jest konwertowana naraz do wskaźnik, jak zwykle (patrz punkt 6), więc druga deklaracja inicjuje p wskazywać na pierwszą nienazwaną tablicę element.

Niektóre Kompilatory mają przełącznik kontrolowanie, czy literały ciągów są zapisywalne czy nie (do kompilacji starych kod), a niektóre mogą mieć opcje do powoduje, że literały ciągów są formalnie traktowane jako tablice const char (dla lepsze wychwytywanie błędów).

 199
Author: matli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-15 08:02:12

Zwykle literały łańcuchów są przechowywane w pamięci tylko do odczytu, gdy program jest uruchomiony. Ma to zapobiec przypadkowej zmianie stałej ciągu. W pierwszym przykładzie {[2] } jest przechowywany w pamięci tylko do odczytu i *str wskazuje na pierwszy znak. Segfault pojawia się, gdy próbujesz zmienić pierwszy znak na 'z'.

W drugim przykładzie łańcuch "string"jest kopiowany przez kompilator z jego domu tylko do odczytu do tablicy str[]. Następnie zmiana pierwszego znaku jest dozwolone. Możesz to sprawdzić, drukując adres każdego z nich:

printf("%p", str);

Również wydrukowanie rozmiaru str w drugim przykładzie pokaże, że kompilator przydzielił mu 7 bajtów:

printf("%d", sizeof(str));
 87
Author: Greg Hewgill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-15 08:04:02

Większość z tych odpowiedzi jest poprawna, ale żeby dodać trochę więcej jasności...

" pamięć tylko do odczytu", do której ludzie odnoszą się, to segment tekstowy w terminach ASM. To to samo miejsce w pamięci, gdzie ładowane są instrukcje. Jest to tylko do odczytu z oczywistych powodów, takich jak bezpieczeństwo. Podczas tworzenia znaku * inicjalizowanego na łańcuch, DANE łańcuchowe są kompilowane do segmentu tekstowego, a program inicjalizuje wskaźnik wskazujący na segment tekstowy. Więc jeśli spróbujesz to zmienić, kaboom. Segfault.

Gdy jest zapisywana jako tablica, kompilator umieszcza inicjalizowane dane łańcuchowe w segmencie danych, który jest tym samym miejscem, w którym żyją Twoje zmienne globalne i takie. Pamięć ta jest zmienna, ponieważ w segmencie danych nie ma instrukcji. Tym razem, gdy kompilator inicjalizuje tablicę znaków (która nadal jest tylko znakiem*), wskazuje ona na segment danych, a nie na segment tekstowy, który można bezpiecznie zmienić w czasie wykonywania.

 28
Author: Bob Somers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-03 10:40:57

W pierwszym kodzie, "string" jest stałą łańcuchową, a stałe łańcuchowe nigdy nie powinny być modyfikowane, ponieważ są często umieszczane w pamięci tylko do odczytu. "str" jest wskaźnikiem używanym do modyfikacji stałej.

W drugim kodzie, "string" jest inicjalizatorem tablicy, rodzajem krótkiej ręki dla

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

"str" jest tablicą przydzieloną na stosie i może być dowolnie modyfikowana.

 16
Author: Andru Luvisi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-02 19:50:27

Dlaczego mam błąd segmentacji podczas pisania do łańcucha?

C99 n1256 projekt

Istnieją dwa zupełnie różne zastosowania liter tablicy:

  1. Initialize char[]:

    char c[] = "abc";      
    

    To jest "więcej magii" i opisane w 6.7.8/14 "Inicjalizacja":

    Tablica typu znakowego może być inicjalizowana literalnym łańcuchem znaków, opcjonalnie zamknięte w szelkach. Kolejne postacie literalny ciąg znaków (w tym kończący znak null, jeśli jest miejsce lub jeśli tablica jest nieznanego rozmiaru) zainicjalizuj elementy tablicy.

    Więc to jest tylko skrót do:

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

    Jak każda inna tablica, c może być modyfikowana.

  2. Everywhere else: generuje:

    Więc kiedy piszesz:

    char *c = "abc";
    

    To jest podobne do:

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

    Zwróć uwagę na ukrytą obsadę od char[] do char *, która jest zawsze legalna.

    Następnie, jeśli zmodyfikujesz c[0], modyfikujesz również __unnamed , czyli UB.

    Jest to udokumentowane w 6.4.5 "literały łańcuchowe":

    5 w fazie translacji 7 do każdego wielobajtu dołączany jest bajt lub kod o wartości zero ciąg znaków, który wyniki z ciągu literalnego lub literałów. Znak wielobajtowy sekwencja jest następnie używana do inicjalizacji tablicy statycznego czasu przechowywania i długości tylko wystarczająca do zatrzymania sekwencji. W przypadku literałów ciągu znaków elementy tablicy mają typu char i są inicjalizowane pojedynczymi bajtami znaku wielobajtowego Sekwencja [...]

    6 nie jest pewne, czy tablice te są odrębne, pod warunkiem, że ich elementy mają odpowiednie wartości. Jeśli program próby modyfikacji takiej tablicy, zachowanie jest undefined.

6.7.8/32 "Inicjalizacja" daje bezpośredni przykład:

Przykład 8: deklaracja

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

Definiuje "zwykłe" obiekty tablicy znaków s i t, których elementy są inicjalizowane literałami ciągu znaków.

Deklaracja ta jest identyczna z

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

Zawartość tablic można modyfikować. Z drugiej strony, deklaracja

char *p = "abc";

Definiuje p z typem "pointer to char" i inicjalizuje go tak, aby wskazywał na obiekt o typie "array of char" o długości 4, którego elementy są inicjalizowane literalnym ciągiem znaków. Jeśli zostanie podjęta próba użycia p do modyfikacji zawartości tablicy, zachowanie jest niezdefiniowane.

GCC 4.8 x86-64 implementacja Linuksa

Zobaczmy, dlaczego ta implementacja segfaults.

Program:

#include <stdio.h>

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

Kompilować i dekompilować:

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

Wyjście zawiera:

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

Tak więc łańcuch jest przechowywany w sekcji .rodata.

Potem:

readelf -l a.out

Zawiera (uproszczone):

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000704 0x0000000000000704  R E    200000

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

Oznacza to, że domyślny skrypt linkera zrzuca zarówno .text, jak i .rodata do segmentu, który można wykonać, ale nie zmodyfikować (Flags = R E). Próba modyfikacji takiego segmentu prowadzi do segfault w Linuksie.

Jeśli zrobimy to samo dla char[]:

 char s[] = "abc";

Otrzymujemy:

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

Jest więc przechowywany w stosie (w stosunku do %rbp) i oczywiście możemy go modyfikować.

 16
Author: Ciro Santilli 新疆改造中心 六四事件 法轮功,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-13 07:22:10

Ponieważ typem "whatever" w kontekście pierwszego przykładu jest const char * (nawet jeśli przypisujesz go do znaku non-const*), co oznacza, że nie powinieneś próbować pisać do niego.

Kompilator wymusił to, umieszczając łańcuch znaków w tylko do odczytu części pamięci, stąd zapis do niego generuje segfault.

 11
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-02 19:49:21

Aby zrozumieć ten błąd lub problem należy najpierw znać różnicę B / W wskaźnika i tablicy więc tutaj najpierw mam wyjaśnić różnice b / w nich

String array

 char strarray[] = "hello";

W pamięci tablica jest przechowywana w ciągłych komórkach pamięci, przechowywana jako [h][e][l][l][o][\0] =>[] jest 1 bajtową komórką pamięci, a do tych ciągłych komórek pamięci można uzyskać dostęp przez nazwę o nazwie strarray here.so tutaj tablica łańcuchowa strarray zawierająca wszystkie znaki łańcucha inicjalizowanego do it.in ta sprawa tutaj "hello" możemy więc łatwo zmienić jego zawartość pamięci, uzyskując dostęp do każdego znaku przez jego wartość indeksu

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

I jego wartość została zmieniona na 'm' Tak więc wartość strarray została zmieniona na "mello";

Należy zauważyć, że możemy zmienić zawartość tablicy łańcuchów zmieniając znak po znaku, ale nie możemy zainicjalizować innego łańcucha bezpośrednio do niej, jak {[9] } jest invalid

Pointer

Jak wszyscy wiemy wskaĺşniki wskazujÄ ... na lokalizacjÄ ™ pamiÄ ™ ci w pamiÄ ™ ci , wskaźnik niezinicjalizowany wskazuje na losową lokalizację pamięci, a po inicjalizacji wskazuje na konkretną lokalizację pamięci

char *ptr = "hello";

Tutaj wskaźnik ptr jest inicjalizowany na string "hello" który jest ciągiem stałym przechowywanym w pamięci tylko do odczytu (ROM), więc "hello" nie można go zmienić, ponieważ jest przechowywany w ROM

I ptr jest przechowywany w sekcji stosu i wskazuje na stały łańcuch "hello"

Więc ptr [0]='m' jest niepoprawne, ponieważ nie można uzyskać dostępu do pamięci tylko do odczytu

Ale ptr może być inicjowany na inny ciąg znaków wartość bezpośrednio, ponieważ jest tylko wskaźnikiem, więc może być wskazywana na dowolny adres pamięci zmiennej jej typu danych

ptr="new string"; is valid
 7
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-07 13:34:30
char *str = "string";  

Powyższe ustawia str, aby wskazywać na literalną wartość "string", która jest mocno zakodowana w binarnym obrazie programu, który prawdopodobnie jest oznaczony jako Tylko do odczytu w pamięci.

Więc str[0]= próbuje zapisać do kodu tylko do odczytu aplikacji. Domyślam się jednak, że jest to prawdopodobnie zależne od kompilatora.

 6
Author: DougN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-13 20:34:02
char *str = "string";

Przydziela wskaźnik do literalnego ciągu znaków, który kompilator umieszcza w niemodyfikowalnej części Twojego pliku wykonywalnego;

char str[] = "string";

Przydziela i inicjalizuje tablicę lokalną, która jest modyfikowalna

 5
Author: Rob Walker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-02 19:51:06

The C FAQ that @matli linked to mention it, but no one else here has yet, so for clearance: if a string literal (Double-quoted string in your source) is used anywhere other than to initialize a character array (tj. drugi przykład @Mark, który działa poprawnie), that string is stored by the compiler in a special static string table, which is like to creating a global static variable (read-only, of course) that is already anymous (has brak zmiennej "name"). Część Tylko do odczytu jest ważną częścią i dlatego pierwszy przykład kodu @Mark segfaults.

 5
Author: rpj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-02 20:00:45

The

 char *str = "string";

Line definiuje wskaźnik i wskazuje go na literalny łańcuch znaków. Literalny ciąg znaków nie jest zapisywalny, więc gdy to zrobisz:

  str[0] = 'z';
Masz usterkę seg. Na niektórych platformach literał może znajdować się w pamięci zapisywalnej, więc nie zobaczysz segfault, ale jest to nieprawidłowy kod (skutkujący niezdefiniowanym zachowaniem) niezależnie od tego.

Linia:

char str[] = "string";

Przydziela tablicę znaków i kopiuje dosłowny łańcuch do tej tablicy, który jest w pełni zapisywalny, więc kolejna aktualizacja nie stanowi problemu.

 3
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-02 19:54:04

Literały łańcuchowe takie jak "string" są prawdopodobnie przydzielane w przestrzeni adresowej programu wykonywalnego jako dane tylko do odczytu (mniej więcej twój kompilator). Kiedy idziesz go dotknąć, przeraża cię to, że jesteś w jego strefie kostiumów kąpielowych i daje Ci znać z błędem seg.

W pierwszym przykładzie otrzymujesz wskaźnik do danych const. W drugim przykładzie inicjalizujesz tablicę 7 znaków z kopią danych const.

 2
Author: Jurney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-02 19:51:59
// 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. 
 1
Author: jokeysmurf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-19 23:00:19

Po pierwsze, str jest wskaźnikiem wskazującym na "string". Kompilator może umieszczać literały łańcuchów w miejscach pamięci, do których nie można pisać, ale można tylko czytać. (To naprawdę powinno wywołać Ostrzeżenie, ponieważ przypisujesz const char * do char *. Masz wyłączone ostrzeżenia, czy po prostu je zignorowałeś?)

Po drugie, tworzysz tablicę, która jest pamięcią, do której masz pełny dostęp, i inicjalizujesz ją za pomocą "string". Tworzysz char[7] (sześć za litery, jeden za końcówkę "\0"), i robisz z nim co chcesz.

 0
Author: David Thornley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-13 20:34:11

Pierwszy jest jednym ciągiem stałym, którego nie można modyfikować. Druga jest tablicą z zainicjalizowaną wartością, więc można ją modyfikować.

 -1
Author: libralhb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-11 08:42:52

Błąd segmentacji jest spowodowany, gdy tyr dostępu do pamięci, która jest niedostępna.

char *str jest wskaźnikiem do ciągu znaków, który nie jest modyfikowalny(powód uzyskania błędu seg)..

Natomiast char str[] jest tablicą i można ją modyfikować..

 -2
Author: Raghu Srikanth Reddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-03 19:03:45