#include<stdio.h>
#include<string.h>
int main()
{
char * p = "abc";
char * p1 = "abc";
printf("%d %d", p, p1);
}
Kiedy drukuję wartości dwóch wskaźników, drukuje ten sam adres. Czemu?
#include<stdio.h>
#include<string.h>
int main()
{
char * p = "abc";
char * p1 = "abc";
printf("%d %d", p, p1);
}
Kiedy drukuję wartości dwóch wskaźników, drukuje ten sam adres. Czemu?
p
i p1
, zauważyłbyś, że te dwa wskaźniki są przechowywane pod dwoma różnymi adresami. To, że ich wartość jest taka sama, jest - w tym przypadku - nieistotne.
p == p1
(nie różnią się), ale &p != &p1
(różnią się).
Odpowiedzi:
To, czy dwa różne literały ciągów o tej samej zawartości są umieszczone w tej samej lokalizacji pamięci, czy w różnych lokalizacjach pamięci, zależy od implementacji.
Zawsze należy traktować p
i p1
jako dwa różne wskaźniki (nawet jeśli mają tę samą treść), ponieważ mogą, ale nie muszą, wskazywać na ten sam adres. Nie powinieneś polegać na optymalizacji kompilatora.
C11 Standard, 6.4.5, literały łańcuchowe, semantyka
Nie jest określone, czy te tablice są różne, pod warunkiem, że ich elementy mają odpowiednie wartości. Jeśli program spróbuje zmodyfikować taką tablicę, zachowanie jest niezdefiniowane.
Format do druku musi być %p
:
printf("%p %p", (void*)p, (void*)p1);
Zobacz tę odpowiedź, aby dowiedzieć się, dlaczego.
i modify one of the pointer, will the data in the other pointed also be modified
Możesz zmodyfikować wskaźnik, ale nie literał ciągu. Np. char *p="abc"; p="xyz";
Jest w porządku, podczas gdy char *p="abc"; p[0]='x';
wywołuje niezdefiniowane zachowanie . To nie ma z tym nic wspólnego volatile
. Niezależnie od tego, czy używasz, volatile
czy nie, nie powinieneś zmieniać żadnego zachowania, które nas tutaj interesuje. volatile
w zasadzie wymusza za każdym razem odczytywanie danych z pamięci.
p
wskazuje na literał ciągu "abc"
i p[0]='x'
próbuje zmodyfikować pierwszy znak literału ciągu. Próba zmodyfikowania literału ciągu jest niezdefiniowanym zachowaniem w C.
char []
w C. Dlatego ustawienie go const char*
jako tylko do odczytu ( jak ma to miejsce w C ++) wymagałoby również zmiany typu . [cd.]
"Strings are no longer modifiable, and so may be placed in read-only memory"
, historyczny dowód, że literały łańcuchowe wykorzystywane być modyfikowalny ;-)
Twój kompilator wydaje się być całkiem sprytny, wykrywając, że oba literały są takie same. A ponieważ literały są stałe, kompilator zdecydował się nie przechowywać ich dwukrotnie.
Warto wspomnieć, że niekoniecznie musi tak być. Proszę zobaczyć Blue Moon „s odpowiedzi na ten temat .
Btw: printf()
Oświadczenie powinno wyglądać tak
printf("%p %p", (void *) p, (void *) p1);
as "%p"
powinno być używane do drukowania wartości wskaźnika i jest definiowane tylko dla wskaźnika typu void *
. * 1
Powiedziałbym również, że kod nie zawiera return
instrukcji, ale wydaje się, że standard C jest w trakcie zmiany. Inni mogą to uprzejmie wyjaśnić.
* 1: Przesyłanie do tego void *
miejsca nie jest konieczne w przypadku char *
wskaźników, ale w przypadku wskaźników do wszystkich innych typów.
==
należy użyć strcmpy()
funkcji. Ponieważ inny kompilator może nie używać optymalizacji (jest to kompilator do góry - zależny od implementacji), jak Alk odpowiedział PS: Blue Moon właśnie o tym dodał.
Twój kompilator wykonał coś, co nazywa się „puli ciągów”. Określiłeś, że chcesz mieć dwa wskaźniki, oba wskazujące na ten sam literał ciągu - więc utworzono tylko jedną kopię literału.
Technicznie: powinien był narzekać na ciebie, że nie podałeś wskaźników „const”
const char* p = "abc";
Dzieje się tak prawdopodobnie dlatego, że używasz programu Visual Studio lub używasz GCC bez -Wall.
Jeśli wyraźnie chcesz, aby były przechowywane dwukrotnie w pamięci, spróbuj:
char s1[] = "abc";
char s2[] = "abc";
Tutaj wyraźnie stwierdzasz, że chcesz mieć dwie tablice znaków c-string, a nie dwa wskaźniki do znaków.
Uwaga: pule ciągów są funkcją kompilatora / optymalizatora, a nie aspektem języka. Jako takie, różne kompilatory w różnych środowiskach będą wytwarzać różne zachowania w zależności od takich rzeczy, jak poziom optymalizacji, flagi kompilatora i to, czy ciągi znaków znajdują się w różnych jednostkach kompilacji.
gcc (Debian 4.4.5-8) 4.4.5
nie narzeka (ostrzega), chociaż używa -Wall -Wextra -pedantic
.
const
literałów łańcuchowych. Ostrzeżenie jest włączone według opcji -Wwrite-strings
. Najwyraźniej nie jest włączana żadną inną opcją (taką jak -Wall
, -Wextra
lub -pedantic
).
Jak powiedzieli inni, kompilator zauważa, że mają one tę samą wartość, więc decyduje się na udostępnienie im danych w ostatecznym pliku wykonywalnym. Ale robi się bardziej wyszukany: kiedy skompiluję następujące zgcc -O
#include<stdio.h>
#include<string.h>
int main()
{
char * p = "abcdef";
char * p1 = "def";
printf("%d %d", p, p1);
}
drukuje 4195780 4195783
dla mnie. Oznacza to, że p1
rozpoczyna się 3 bajty później p
, więc GCC zobaczył wspólny sufiks def
(łącznie z \0
terminatorem) i przeprowadził podobną optymalizację do tej, którą pokazałeś.
(To jest odpowiedź, ponieważ komentarz jest za długi).
Literały łańcuchowe w kodzie są przechowywane w segmencie danych tylko do odczytu w kodzie. Kiedy zapisujesz ciąg znaków, taki jak „abc”, w rzeczywistości zwraca on „const char *” i gdybyś miał wszystkie ostrzeżenia kompilatora, powiedziałby ci, że w tym momencie rzutujesz. Nie możesz zmieniać tych ciągów z tego samego powodu, który wskazałeś w tym pytaniu.
Kiedy tworzysz literał ciągu znaków („abc”), jest on zapisywany w pamięci, która zawiera literały łańcuchowe, a następnie jest ponownie używany, jeśli odwołujesz się do tego samego literału ciągu, a zatem oba wskaźniki wskazują to samo miejsce, gdzie „ abc "literał ciągu znaków jest przechowywany.
Nauczyłem się tego jakiś czas temu, więc może nie wyjaśniłem tego naprawdę jasno, przepraszam.
W rzeczywistości zależy to od używanego kompilatora .
W moim systemie z TC ++ 3.5 wypisuje dwie różne wartości dla dwóch wskaźników, tj. Dwa różne adresy .
Twój kompilator jest tak zaprojektowany, aby sprawdzał istnienie jakiejkolwiek wartości w pamięci iw zależności od jej istnienia ponownie przypisał lub użył tego samego odwołania do poprzednio zapisanej wartości, jeśli odniesiono się do tej samej wartości.
Więc nie myśl o tym zbyt wiele, ponieważ zależy to od sposobu, w jaki kompilator analizuje kod.
Jest to optymalizacja kompilatora, ale zapomnij o optymalizacji pod kątem przenośności. Czasami skompilowane kody są bardziej czytelne niż rzeczywiste kody.