Jaka jest różnica między znakiem s [] a znakiem *s?

W języku C, można użyć literału łańcuchowego w deklaracji takiej jak:

char s[] = "hello";

Lub tak:

char *s = "hello";
Więc jaka jest różnica? Chcę wiedzieć, co tak naprawdę dzieje się pod względem czasu przechowywania, zarówno w czasie kompilacji, jak i uruchamiania.
Author: StoryTeller, 2009-11-10

12 answers

Różnica polega na tym, że

char *s = "Hello world";

Umieści "Hello world" w Tylko do odczytu części pamięci, a uczynienie s wskaźnikiem do tego sprawia, że każda operacja zapisu na tej pamięci jest nielegalna.

Podczas wykonywania:

char s[] = "Hello world";

Umieszcza literalny łańcuch w pamięci tylko do odczytu i kopiuje go do nowo przydzielonej pamięci na stosie. W ten sposób powstaje

s[0] = 'J';

Prawne.

 487
Author: Rickard,
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-03-11 14:28:58

Po pierwsze, w argumentach funkcji są one dokładnie równoważne:

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

W innych kontekstach char * przydziela wskaźnik, podczas gdy char [] przydziela tablicę. Gdzie sznurek idzie w poprzednim przypadku, pytasz? Kompilator potajemnie przydziela statyczną anonimową tablicę, która przechowuje ciąg znaków literalnych. Więc:

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

Zauważ, że nigdy nie wolno próbować modyfikować zawartości tej anonimowej tablicy za pomocą tego wskaźnika; efekty są niezdefiniowane (często oznaczające crash): {]}

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

Użycie składni tablicy bezpośrednio przydziela ją do nowej pamięci. Tak więc modyfikacja jest Bezpieczna:

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

Jednak tablica żyje tylko tak długo, jak jej zakres kontanowania, więc jeśli robisz to w funkcji, nie zwracaj lub nie wyciekaj wskaźnika do tej tablicy - zrób kopię za pomocą strdup() lub podobnego. Jeśli tablica jest przydzielona w zasięgu globalnym, oczywiście nie ma problemu.

 136
Author: bdonlan,
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
2017-12-02 17:52:30

Ta deklaracja:

char s[] = "hello";

Tworzy Jeden obiekt - A char tablica o rozmiarze 6, nazywana s, zainicjalizowana wartościami 'h', 'e', 'l', 'l', 'o', '\0'. Miejsce przydzielenia tej tablicy w pamięci i jak długo będzie ona żyła, zależy od tego, gdzie pojawi się deklaracja. Jeśli deklaracja znajduje się wewnątrz funkcji, będzie żyć do końca bloku, w którym jest zadeklarowana, i prawie na pewno zostanie przydzielona na stosie; jeśli jest poza funkcją, to prawdopodobnie będzie przechowywana wewnątrz "inicjalizowany segment danych", który jest ładowany z pliku wykonywalnego do pamięci zapisywalnej podczas uruchamiania programu.

Z drugiej strony, ta deklaracja:

char *s ="hello";

Tworzy dwa obiekty:

  • a Tylko do odczytu tablica 6 char S zawierająca wartości 'h', 'e', 'l', 'l', 'o', '\0', która nie ma nazwy i ma statyczny czas przechowywania (co oznacza, że żyje przez cały okres życia programu); oraz
  • zmienna typu pointer-to-char, o nazwie s, która jest inicjalizowana lokalizacją pierwszego znaku w tej nienazwanej tablicy tylko do odczytu.

Nienazwana tablica tylko do odczytu zazwyczaj znajduje się w segmencie" tekst " programu, co oznacza, że jest ładowana z dysku do pamięci tylko do odczytu, wraz z samym kodem. Położenie zmiennej s w pamięci zależy od tego, gdzie pojawia się deklaracja (tak jak w pierwszym przykładzie).

 62
Author: caf,
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
2009-11-09 22:50:25

Biorąc pod uwagę deklaracje

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

Załóżmy następującą hipotetyczną mapę pamięci:

                    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

Ciąg znaków "hello world" jest 12-elementową tablicą char (const char W C++) ze statycznym czasem przechowywania, co oznacza, że pamięć dla niego jest przydzielana po uruchomieniu programu i pozostaje przydzielana aż do jego zakończenia. Próba zmodyfikowania zawartości ciągu literalnego wywołuje nieokreślone zachowanie.

Linia

char *s0 = "hello world";

Definiuje s0 jako wskaźnik do char z automatycznym czasem przechowywania (co oznacza, że zmienna s0 istnieje tylko dla zakresu, w którym jest zadeklarowana) i kopiuje do niej adres ciągu literalnego (0x00008000 w tym przykładzie). Zauważ, że ponieważ s0 wskazuje na literalny łańcuch znaków, nie powinien być używany jako argument do żadnej funkcji, która próbowałaby go zmodyfikować (np., strtok(), strcat(), strcpy(), itd.).

Linia

char s1[] = "hello world";

Definiuje s1 jako 12-elementową tablicę char (długość jest pobierana z ciągu dosłowne) z automatycznym czasem przechowywania i kopiuje zawartość dosłownego do tablicy. Jak widać z mapy pamięci, mamy dwie kopie łańcucha "hello world"; różnica polega na tym, że można zmodyfikować łańcuch zawarty w s1.

s0 i s1 są wymienne w większości kontekstów; oto wyjątki:

sizeof s0 == sizeof (char*)
sizeof s1 == 12

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

Możesz przypisać zmienną s0 tak, aby wskazywała na inny ciąg znaków lub inną zmienną. Nie można ponownie przypisać zmiennej s1 aby wskazać inną tablicę.

 53
Author: John Bode,
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
2015-03-26 15:22:09

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 znaki ciągu 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 napisz:

    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 sekwencja znaków, która wynika z ciągu literałów lub literałów. Znak wielobajtowy sekwencja jest następnie używany 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óbuje zmodyfikować taką tablicę, 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 "wskaźnik do znaku" i inicjalizuje go tak, aby wskazywał na obiekt o typie" array of char " o długości 4, którego elementy są inicjalizowane literalnym łańcuchem znaków. Jeśli zostanie podjęta próba użycia p do modyfikacji zawartości tablicy, zachowanie jest niezdefiniowane.

Implementacja GCC 4.8 x86-64 ELF

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

Wniosek: GCC przechowuje char* it w .rodata sekcja, nie W .text.

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

Zauważ jednak, że domyślny skrypt linkera umieszcza .rodata i .text w tym samym segmencie, który ma polecenie execute, ale nie ma uprawnień do zapisu. Można to zaobserwować za pomocą:

readelf -l a.out

Który zawiera:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata 
 27
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:55
char s[] = "hello";

Deklaruje s jako tablicę char, która jest wystarczająco długa, aby trzymać inicjalizator (5 + 1 char s) I inicjalizuje tablicę przez skopiowanie członków podanego ciągu literalnego do tablicy.

char *s = "hello";

Deklaruje s jako wskaźnik do jednego lub więcej (w tym przypadku więcej) chars i wskazuje bezpośrednio na stałe (tylko do odczytu) miejsce zawierające literał "hello".

 15
Author: CB Bailey,
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
2015-09-05 14:12:10
char s[] = "Hello world";

Tutaj, s jest tablica znaków, które mogą być nadpisane, jeśli chcemy.

char *s = "hello";

Literał Łańcuchowy jest używany do tworzenia tych bloków znaków gdzieś w pamięci, na którą wskazuje ten wskaźnik s. Możemy tutaj ponownie przypisać obiekt, do którego wskazuje, zmieniając go, ale tak długo, jak wskazuje na literalny ciąg znaków, blok znaków, na który wskazuje, nie może zostać zmieniony.

 4
Author: Sailaja,
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-04-27 15:10:58

Jako dodatek, weź pod uwagę, że dla celów tylko do odczytu użycie obu jest identyczne, możesz uzyskać dostęp do znaku przez indeksowanie za pomocą [] lub *(<var> + <index>) format:

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

I:

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

Oczywiście, jeśli spróbujesz zrobić

*(x + 1) = 'a';

Prawdopodobnie wystąpi błąd segmentacji, ponieważ próbujesz uzyskać dostęp do pamięci tylko do odczytu.

 3
Author: Nick Louloudakis,
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
2015-11-30 10:22:03

Aby dodać: otrzymujesz również różne wartości dla ich rozmiarów.

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

Jak wspomniano powyżej, tablica '\0' zostanie przydzielona jako element końcowy.

 3
Author: Muzab,
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
2017-03-31 15:28:44
char *str = "Hello";

Powyższe ustawienie str wskazuje na literalną wartość "Hello", która jest zakodowana na twardo w obrazie binarnym programu, który jest oznaczany jako Tylko do odczytu w pamięci, oznacza, że każda zmiana w tym Literale jest nielegalna i powodowałaby błędy segmentacji.

char str[] = "Hello";

Kopiuje łańcuch znaków do nowo przydzielonej pamięci na stosie. W ten sposób dokonywanie jakichkolwiek zmian w nim jest dozwolone i legalne.

means str[0] = 'M';

Zmieni str na "Mello".

Aby uzyskać więcej informacji, proszę przejść przez podobne pytanie:

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

 2
Author: Mohit,
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
2017-05-23 12:26:36

W przypadku:

char *x = "fred";

X jest lvalue -- może być przypisany do. Ale w przypadku:

char x[] = "fred";

X nie jest lvalue, jest rvalue - nie można do niego przypisać.

 0
Author: Lee-Man,
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
2009-11-09 22:57:04

W świetle komentarzy tutaj powinno być oczywiste, że: char * s = " hello" ; To zły pomysł i powinien być stosowany w bardzo wąskim zakresie.

To może być dobra okazja, by zwrócić uwagę, że "constitution" to "dobra rzecz". Kiedykolwiek i gdziekolwiek możesz, użyj słowa kluczowego "const", aby chronić swój kod, przed "zrelaksowanymi" rozmówcami lub programistami, którzy zwykle są najbardziej "zrelaksowani", gdy wchodzą w grę wskaźniki.

Dość melodramatu, oto co można osiągnąć przy ozdabianiu wskaźniki z "const". (Uwaga: należy odczytywać deklaracje wskaźnika od prawej do lewej.) Oto 3 różne sposoby ochrony się podczas gry wskaźnikami:
const DBJ* p means "p points to a DBJ that is const" 

- czyli obiekt DBJ nie może być zmieniany przez p.

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

- oznacza to, że możesz zmienić obiekt DBJ poprzez p, ale nie możesz zmienić samego wskaźnika P.

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

- oznacza to, że nie można zmienić samego wskaźnika p, ani nie można zmienić obiektu DBJ poprzez p.

Błędy związane z próbą mutacje const-ant są wyłapywane w czasie kompilacji. Nie ma miejsca na wykonanie ani kary za prędkość dla const.

(założenie, że używasz kompilatora C++, oczywiście ?)

--DBJ

 0
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
2009-11-09 23:27:55