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. 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.
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.
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).
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ę.
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:
-
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. -
Everywhere else: generuje:
- bez nazwy
- array of char jaki jest typ literałów łańcuchowych w C i C++?
- Z pamięcią statyczną
- to daje UB jeśli zmodyfikowane
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[]
dochar *
, 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
it
, 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życiap
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
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) char
s i wskazuje bezpośrednio na stałe (tylko do odczytu) miejsce zawierające literał "hello"
.
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.
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.
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.
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:
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ć.
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