Interesuje mnie, gdzie są przydzielane / przechowywane literały ciągów.
Znalazłem tutaj jedną intrygującą odpowiedź , mówiącą:
Zdefiniowanie ciągu w linii faktycznie osadza dane w samym programie i nie można go zmienić (niektóre kompilatory pozwalają na to sprytną sztuczką, nie przejmuj się).
Ale miało to coś wspólnego z C ++, nie wspominając o tym, że mówi, żeby nie zawracać sobie głowy.
Przeszkadzam. = D
Więc moje pytanie brzmi: gdzie i jak jest przechowywany mój dosłowny ciąg? Dlaczego nie powinienem próbować tego zmieniać? Czy implementacja różni się w zależności od platformy? Czy ktoś chce rozwinąć „sprytną sztuczkę”?
źródło
foo = "hello"
W tym przypadku) może spowodować niezamierzone efekty uboczne ... (zakładając, że nie alokowanie pamięci znew
czy czymś)char *p = "abc";
do tworzenia zmiennych ciągów, jak inaczej powiedział @ChrisCooperNie ma na to jednej odpowiedzi. Standardy C i C ++ mówią po prostu, że literały ciągów mają statyczny czas trwania, każda próba ich modyfikacji daje niezdefiniowane zachowanie, a wiele literałów ciągów o tej samej zawartości może, ale nie musi, współużytkować tę samą pamięć.
W zależności od systemu, dla którego piszesz, i możliwości formatu pliku wykonywalnego, którego używa, mogą być one przechowywane wraz z kodem programu w segmencie tekstowym lub mogą mieć oddzielny segment dla zainicjowanych danych.
Określenie szczegółów będzie się różnić w zależności od platformy - najprawdopodobniej obejmują narzędzia, które mogą powiedzieć, gdzie je umieszcza. Niektóre nawet dadzą ci kontrolę nad takimi szczegółami, jeśli chcesz (np. Gnu ld pozwala ci dostarczyć skrypt, który powie wszystko o tym, jak grupować dane, kod itp.)
źródło
movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)
na łańcuchu"AB"
, ale większość czasu, to będzie w segmencie non-kodu, takich jak.data
lub.rodata
czy podobnego (w zależności od tego, czy podpór docelowych segmenty tylko do odczytu).Dlaczego nie powinienem próbować tego zmieniać?
Ponieważ jest to niezdefiniowane zachowanie. Cytat z C99 N1256, szkic 6.7.8 / 32 "Inicjalizacja" :
Dokąd oni poszli?
GCC 4.8 x86-64 ELF Ubuntu 14.04:
char s[]
: stoschar *s
:.rodata
sekcja pliku obiektowego.text
jest zrzucana sekcja pliku obiektowego, która ma uprawnienia do odczytu i wykonywania, ale nie ma uprawnień do zapisuProgram:
Kompiluj i dekompiluj:
Wyjście zawiera:
Więc ciąg jest przechowywany w
.rodata
sekcji.Następnie:
Zawiera (uproszczone):
Oznacza to, że domyślny skrypt konsolidujący zrzuca zarówno
.text
i.rodata
do segmentu, który można wykonać, ale nie można go modyfikować (Flags = R E
). Próba zmodyfikowania takiego segmentu prowadzi do segfaulta w Linuksie.Jeśli zrobimy to samo dla
char[]
:otrzymujemy:
więc zostaje zapisany na stosie (względem
%rbp
) i oczywiście możemy go zmodyfikować.źródło
FYI, po prostu zrób kopię zapasową innych odpowiedzi:
Norma: ISO / IEC 14882: 2003 mówi:
źródło
gcc tworzy
.rodata
sekcję, która jest mapowana „gdzieś” w przestrzeni adresowej i jest oznaczona jako tylko do odczytu,Visual C ++ (
cl.exe
) tworzy.rdata
sekcję w tym samym celu.Możesz spojrzeć na dane wyjściowe z
dumpbin
lubobjdump
(w systemie Linux), aby zobaczyć sekcje swojego pliku wykonywalnego.Na przykład
źródło
printf("some null terminated static string");
zamiastprintf(*address);
w C)To zależy od formatu twojego pliku wykonywalnego . Jednym ze sposobów myślenia o tym jest to, że jeśli zajmujesz się programowaniem w asemblerze, możesz umieścić literały łańcuchowe w segmencie danych swojego programu asemblerowego. Twój kompilator C robi coś takiego, ale wszystko zależy od tego, dla jakiego systemu tworzony jest plik binarny.
źródło
Literały ciągów są często przydzielane do pamięci tylko do odczytu, dzięki czemu są niezmienne. Jednak w niektórych kompilatorach modyfikacja jest możliwa dzięki "sprytnej sztuczce" .. A sprytna sztuczka polega na "użyciu wskaźnika znakowego wskazującego na pamięć" ... pamiętaj, że niektóre kompilatory mogą na to nie pozwalać ... Oto demo
źródło
Ponieważ może się to różnić w zależności od kompilatora, najlepszym sposobem jest przefiltrowanie zrzutu obiektu dla wyszukanego literału ciągu:
gdzie
-s
wymuszaobjdump
wyświetlenie pełnej zawartości wszystkich sekcji,main.o
jest plikiem obiektowym,-B 1
wymuszagrep
również wypisanie jednej linii przed dopasowaniem (abyś mógł zobaczyć nazwę sekcji) istr
jest literałem ciągu, którego szukasz.Z gcc na komputerze z systemem Windows i jedną zmienną zadeklarowaną w
main
likebieganie
zwroty
źródło