Adresy dwóch wskaźników znaków do różnych literałów ciągów są takie same

80

Kiedy drukuję wartości dwóch wskaźników, drukuje ten sam adres. Czemu?

seereddi sekhar
źródło
66
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.

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:

Zobacz tę odpowiedź, aby dowiedzieć się, dlaczego.

PP
źródło
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.

Warto wspomnieć, że niekoniecznie musi tak być. Proszę zobaczyć Blue Moon „s odpowiedzi na ten temat .


Btw: printf()Oświadczenie powinno wyglądać tak

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 returninstrukcji, 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.

alk
źródło
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”

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:

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.

kfsone
źródło
1
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).

huon
źródło
3

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.

Salgar
źródło
2

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.

Lord Zsolt
źródło
2

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

Rajesh Paul
źródło
1

ponieważ napis „abc” jest adresem w pamięci. kiedy ponownie napiszesz "abc", zapisze ten sam adres

SANDEEP
źródło
1

Jest to optymalizacja kompilatora, ale zapomnij o optymalizacji pod kątem przenośności. Czasami skompilowane kody są bardziej czytelne niż rzeczywiste kody.

Amir Saniyan
źródło
0

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.

Dev
źródło