Jak myślisz, dlaczego nie powinno? Te oba wskaźniki wskazują dokładnie to samo. To, co widzisz, jest prawdopodobnie efektem techniki optymalizacji zwanej łączeniem ciągów.
Daniel Kamil Kozar
2
Chociaż dane są takie same, ale zmienne są różne.
seereddi sekhar
2
Zmienne są oczywiście różne. Gdybyś wziął adres pi 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.
Daniel Kamil Kozar
Tak, jeśli zmienię wartości, adresy są inne.
seereddi sekhar
11
@JanHudec: Przeczytaj ponownie pytanie. W tym przypadku (ze względu na optymalizację kompilatora) p == p1(nie różnią się), ale &p != &p1(różnią się).
MSalters
Odpowiedzi:
87
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ć pi p1jako 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.
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.
Użyłem volatile, więc nie może być optymalizacji pamięci, nawet jeśli mają ten sam adres. Jedną z kwestii jest to, że jeśli zmodyfikuję jeden ze wskaźników, dane w drugim wskazanym również zostaną zmodyfikowane.
Megharaj
8
@Megharaj i modify one of the pointer, will the data in the other pointed also be modifiedMoż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, volatileczy nie, nie powinieneś zmieniać żadnego zachowania, które nas tutaj interesuje. volatilew zasadzie wymusza za każdym razem odczytywanie danych z pamięci.
PP
2
@MSharathHegde Yes. Ponieważ pwskazuje 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.
PP
2
@MSharathHegde Ponieważ standard C to stwierdza. Powód jest głównie historyczny, ponieważ przed standardowym językiem C można było modyfikować literały łańcuchowe. Później standard C (C89) uczynił go niezdefiniowanym, więc nowy kod tego nie robi, a stary kod (przedstandardowy) działa tak, jak był. Zasadniczo uważam, że kompromisem jest nie łamanie istniejącego (przedstandardowego) kodu. Innym powodem jest to, że typ literału łańcuchowego znajduje się 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.]
PP
7
Jest to linia K & R 2nd edition w dodatku C: "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 ;-)
PP
28
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.
Dzięki. Więc wniosek jest taki, że optymalizacja kompilatora, prawda? w C funkcja główna domyślnie zwraca 0
seereddi sekhar
@seereddisekhar: Tak, to rodzaj optymalizacji.
alk
2
@seereddisekhar Ale należy uważać, to nie oznacza, że należy porównać dwa ciągi (nawet wskaźnika) z użyciem ==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ł.
Grijesh Chauhan
2
Drogi @Megharaj: Czy mogę prosić o zadanie osobnego pytania w tej sprawie? Możesz zamieścić link do tego nowego pytania tutaj jako komentarz.
alk
1
@Megharaj: Nie można zmienić wartości literału ciągu. Jak wspomniałem w swoim pytaniu, jest stały.
alk
18
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”
constchar* 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.5nie narzeka (ostrzega), chociaż używa -Wall -Wextra -pedantic.
alk
1
Tak, od wersji 4.8.1 gcc domyślnie nie ostrzega o nieużywaniu constliterałó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, -Wextralub -pedantic).
sleske
1
Zarówno GCC 4.4.7, jak i 4.7.2 dają mi ostrzeżenie z lub bez -Wall. pastebin.com/1DtYEzUN
kfsone
15
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
drukuje 4195780 4195783dla mnie. Oznacza to, że p1rozpoczyna się 3 bajty później p, więc GCC zobaczył wspólny sufiks def(łącznie z \0terminatorem) 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.
p
ip1
, 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
ip1
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
Format do druku musi być
%p
:printf("%p %p", (void*)p, (void*)p1);
Zobacz tę odpowiedź, aby dowiedzieć się, dlaczego.
źródło
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 gdychar *p="abc"; p[0]='x';
wywołuje niezdefiniowane zachowanie . To nie ma z tym nic wspólnegovolatile
. 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"
ip[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 goconst 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ć takprintf("%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 typuvoid *
. * 1Powiedział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 przypadkuchar *
wskaźników, ale w przypadku wskaźników do wszystkich innych typów.źródło
==
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.
źródło
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 z
gcc -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, żep1
rozpoczyna się 3 bajty późniejp
, więc GCC zobaczył wspólny sufiksdef
(łącznie z\0
terminatorem) i przeprowadził podobną optymalizację do tej, którą pokazałeś.(To jest odpowiedź, ponieważ komentarz jest za długi).
źródło
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.
źródło
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.
źródło
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.
TO WSZYSTKO...
źródło
ponieważ napis „abc” jest adresem w pamięci. kiedy ponownie napiszesz "abc", zapisze ten sam adres
źródło
Jest to optymalizacja kompilatora, ale zapomnij o optymalizacji pod kątem przenośności. Czasami skompilowane kody są bardziej czytelne niż rzeczywiste kody.
źródło
używasz literału ciągu,
gdy podmiot odpowiedzialny złapie dwa takie same literały ciągu,
podaje to samo miejsce w pamięci, dlatego pokazuje to samo położenie wskaźnika.
źródło