Próbuję zrozumieć wskaźniki w C, ale obecnie jestem mylony z następującymi:
char *p = "hello"
Jest to wskaźnik char wskazujący na tablicę znaków, zaczynający się od h .
char p[] = "hello"
To tablica, która przechowuje cześć .
Jaka jest różnica, gdy przekazuję obie te zmienne do tej funkcji?
void printSomething(char *p)
{
printf("p: %s",p);
}
char p[3] = "hello";
Ciąg inicjalizujący jest zbyt długi dla rozmiaru deklarowanej tablicy. Literówka?char p[]="hello";
może wystarczy!char
specyficzne.Odpowiedzi:
char*
ichar[]
są różnymi typami , ale nie we wszystkich przypadkach jest to od razu widoczne. Wynika to z tego, że tablice rozpadają się na wskaźniki , co oznacza, że jeśli wyrażenie typuchar[]
jest podane tam, gdzie jedno z typówchar*
oczekuje się , kompilator automatycznie konwertuje tablicę na wskaźnik na swój pierwszy element.Przykładowa funkcja
printSomething
oczekuje wskaźnika, więc jeśli spróbujesz przekazać do niej tablicę w następujący sposób:Kompilator udaje, że to napisałeś:
źródło
printf
obsługuje się%s
ciąg formatu: zacznij od podanego adresu i kontynuuj aż do napotkania pustego terminatora. Jeśli chcesz wydrukować tylko jeden znak, możesz%c
na przykład użyć ciągu formatu.char *p = "abc";
znak NULL\0
jest automatycznie dołączany, jak w przypadku tablicy char []?char *name; name="123";
ale mogę zrobić to samo zint
typem? I po użyciu%c
, aby wydrukowaćname
, wyjście jest nieczytelny ciąg znaków:�
?Zobaczmy:
foo * i foo [] są różnymi typami i są obsługiwane przez kompilator w różny sposób (wskaźnik = adres + reprezentacja typu wskaźnika, tablica = wskaźnik + opcjonalna długość tablicy, jeśli jest znana, na przykład, jeśli tablica jest przydzielana statycznie ), szczegóły można znaleźć w standardzie. A na poziomie środowiska uruchomieniowego nie ma między nimi żadnej różnicy (w asemblerze, cóż, prawie, patrz poniżej).
Istnieje również pokrewny w C FAQ pytanie :
źródło
Projekt C99 N1256
Istnieją dwa różne zastosowania literałów ciągów znaków:
Zainicjuj
char[]
:Jest to „więcej magii” i opisane w 6.7.8 / 14 „Inicjalizacja”:
To tylko skrót do:
Jak każda inna zwykła tablica,
c
może być modyfikowana.Wszędzie indziej: generuje:
Więc kiedy piszesz:
Jest to podobne do:
Zwróć uwagę na niejawne przesłanie od
char[]
dochar *
, co jest zawsze legalne.Następnie, jeśli zmodyfikujesz
c[0]
, zmodyfikujesz również__unnamed
, czyli UB.Jest to udokumentowane w 6.4.5 „Literały łańcuchowe”:
6.7.8 / 32 „Inicjalizacja” daje bezpośredni przykład:
Implementacja GCC 4.8 x86-64 ELF
Program:
Kompiluj i dekompiluj:
Dane wyjściowe zawierają:
Wniosek: GCC przechowuje
char*
go w.rodata
sekcji, a nie w.text
.Jeśli zrobimy to samo dla
char[]
:otrzymujemy:
więc zostaje zapisany na stosie (względem
%rbp
).Zauważ jednak, że domyślny skrypt linkera umieszcza
.rodata
i.text
w tym samym segmencie, który wykonał, ale nie ma uprawnień do zapisu. Można to zaobserwować przy:który zawiera:
źródło
Nie wolno zmieniać zawartości stałej ciągu, na co
p
wskazują pierwsze . Drugip
to tablica zainicjowana stałą ciąg, i można zmienić jego zawartość.źródło
W takich przypadkach efekt jest taki sam: w końcu podajesz adres pierwszego znaku w ciągu znaków.
Deklaracje oczywiście nie są takie same.
Poniżej znajduje się pamięć dla ciągu znaków, a także wskaźnik znaków, a następnie inicjuje wskaźnik, aby wskazywał pierwszy znak w ciągu.
Podczas gdy poniższe odkłada pamięć tylko na ciąg. Dzięki temu może zużywać mniej pamięci.
źródło
O ile pamiętam, tablica jest w rzeczywistości grupą wskaźników. Na przykład
to prawdziwe stwierdzenie
źródło
*(arr + 1)
przenosi Cię do drugiego członkaarr
. Jeśli*(arr)
wskazuje na 32-bitowy adres pamięci, np.bfbcdf5e
Wtedy*(arr + 1)
wskazuje nabfbcdf60
(drugi bajt). Dlatego wyjście poza zakres tablicy doprowadzi do dziwnych rezultatów, jeśli system operacyjny nie ulegnie awarii. Jeśliint a = 24;
jest pod adresembfbcdf62
, dostęparr[2]
może się zwrócić24
, zakładając, że segfault nie nastąpi wcześniej.Z APUE , sekcja 5.14:
Cytowany tekst pasuje do wyjaśnienia @Ciro Santilli.
źródło
char p[3] = "hello"
? należychar p[6] = "hello"
pamiętać, że na końcu „łańcucha” w C. znajduje się znak „\ 0”w każdym razie tablica w C jest tylko wskaźnikiem do pierwszego obiektu obiektów dopasowujących w pamięci. jedyne różne są w semantyce. podczas gdy możesz zmienić wartość wskaźnika, aby wskazywał na inną lokalizację w pamięci, tablica po utworzeniu zawsze będzie wskazywać na tę samą lokalizację.
również podczas korzystania z tablic „nowe” i „usuń” są wykonywane automatycznie.
źródło