Aby odpowiedzieć na pytanie, które opublikowałeś w kilku komentarzach (które moim zdaniem należy edytować w swoim poście):
To, czego nie rozumiem, to skąd komputer wie, kiedy odczytuje wartość zmiennej i adres, na przykład 10001, jeśli jest wartością int lub char. Wyobraź sobie, że klikam program o nazwie anyprog.exe. Kod natychmiast zaczyna działać. Czy ten plik exe zawiera informacje o tym, czy zmienne są przechowywane jako w lub char?
Dodajmy do tego trochę kodu. Powiedzmy, że piszesz:
int x = 4;
Załóżmy, że jest przechowywany w pamięci RAM:
0x00010004: 0x00000004
Pierwsza część to adres, druga część to wartość. Kiedy twój program (który wykonuje się jako kod maszynowy) działa, widzi 0x00010004
tylko wartość 0x000000004
. Nie „zna” tego typu danych i nie wie, w jaki sposób „powinien” zostać użyty.
Jak więc twój program wymyślił właściwą rzecz? Rozważ ten kod:
int x = 4;
x = x + 5;
Mamy tutaj przeczytanie i napisanie. Gdy program odczytuje x
z pamięci, znajduje się 0x00000004
tam. A twój program wie, jak go dodać 0x00000005
. Powodem, dla którego Twój program „wie”, że jest to poprawna operacja, jest to, że kompilator zapewnia poprawność operacji dzięki bezpieczeństwu typu. Twój kompilator już zweryfikował, że możesz dodać 4
i 5
razem. Kiedy więc uruchamia się twój kod binarny (exe), nie trzeba go weryfikować. Po prostu wykonuje każdy krok na ślepo, zakładając, że wszystko jest w porządku (złe rzeczy zdarzają się, gdy w rzeczywistości nie są w porządku).
Inny sposób myślenia o tym jest taki. Dam ci te informacje:
0x00000004: 0x12345678
Taki sam format jak poprzednio - adres po lewej, wartość po prawej. Jakiego typu jest wartość? W tym momencie znasz tyle samo informacji o tej wartości, co twój komputer, gdy wykonuje kod. Gdybym kazał ci dodać 12743 do tej wartości, możesz to zrobić. Nie masz pojęcia, jakie będą konsekwencje tej operacji dla całego systemu, ale dodanie dwóch liczb to coś, w czym jesteś naprawdę dobry, więc możesz to zrobić. Czy to sprawia, że wartość jest an int
? Niekoniecznie - widać tylko 32-bitowe wartości i operator dodawania.
Być może pewnym zamieszaniem jest odzyskanie danych. Jeśli mamy:
char A = 'a';
Skąd komputer wie, że wyświetla się a
w konsoli? Jest na to wiele kroków. Pierwszym z nich jest przejście do A
lokalizacji w pamięci i odczytanie jej:
0x00000004: 0x00000061
Wartość szesnastkowa a
w ASCII wynosi 0x61, więc powyższe może być czymś, co zobaczysz w pamięci. Teraz nasz kod maszynowy zna wartość całkowitą. Skąd wie, że zmienia wartość całkowitą w znak, aby ją wyświetlić? Mówiąc najprościej, kompilator wykonał wszystkie niezbędne kroki, aby dokonać tego przejścia. Ale sam komputer (lub program / exe) nie ma pojęcia, jaki jest typ tych danych. Ta 32-bitowa wartość może być dowolna - int
, char
połowa double
, wskaźnik, część tablicy, część string
, część instrukcji itp.
Oto krótka interakcja Twojego programu (exe) z komputerem / systemem operacyjnym.
Program: chcę zacząć. Potrzebuję 20 MB pamięci.
System operacyjny: znajduje 20 wolnych MB pamięci, które nie są używane, i przekazuje je
(Ważna uwaga jest taka, że może to zwrócić dowolne 20 wolnych MB pamięci, nie muszą nawet być ciągłe. W tym momencie program może teraz działać w pamięci, którą posiada, bez rozmowy z systemem operacyjnym)
Program: Zakładam, że pierwszym miejscem w pamięci jest 32-bitowa zmienna całkowita x
.
(Kompilator upewnia się, że dostęp do innych zmiennych nigdy nie dotknie tego miejsca w pamięci. W systemie nic nie mówi, że pierwszy bajt jest zmienny x
lub ta zmienna x
jest liczbą całkowitą. Analogia: masz torbę. Mówisz ludziom, że umieścisz w tej torbie tylko żółte kulki. Gdy ktoś później wyciągnie coś z torby, szokujące byłoby wyciągnięcie czegoś niebieskiego lub sześcianu - coś poszło strasznie nie tak. To samo dotyczy komputerów: twój program przyjmuje teraz, że pierwszym miejscem w pamięci jest zmienna x i że jest liczbą całkowitą. Jeśli w tym bajcie pamięci zostanie kiedykolwiek napisane coś innego lub zakłada się, że jest to coś innego - wydarzyło się coś strasznego. Kompilator zapewnia, że takie rzeczy nie się zdarzyło)
Program: Teraz napiszę 2
do pierwszych czterech bajtów, w których, jak zakładam, x
jest.
Program: Chcę dodać 5 do x
.
Odczytuje wartość X do rejestru tymczasowego
Dodaje 5 do rejestru tymczasowego
Przechowuje wartość rejestru tymczasowego z powrotem w pierwszym bajcie, który nadal jest przyjmowany x
.
Program: założę, że następnym dostępnym bajtem jest zmienna char y
.
Program: Napiszę a
do zmiennej y
.
Biblioteka służy do znalezienia wartości bajtu dla a
Bajt jest zapisywany na adres, który zakłada program y
.
Program: Chcę wyświetlić zawartość y
Odczytuje wartość w drugim miejscu pamięci
Używa biblioteki do konwersji z bajtu na znak
Używa bibliotek graficznych do zmiany ekranu konsoli (ustawianie pikseli z czarnego na biały, przewijanie jednej linii itp.)
(I zaczyna się stąd)
To, co prawdopodobnie Cię rozłącza, to - co dzieje się, gdy nie ma już pierwszego miejsca w pamięci x
? czy drugi już nie jest y
? Co się dzieje, gdy ktoś czyta x
jako wskaźnik char
lub y
wskaźnik? Krótko mówiąc, zdarzają się złe rzeczy. Niektóre z tych rzeczy mają dobrze zdefiniowane zachowanie, a niektóre mają niezdefiniowane zachowanie. Nieokreślone zachowanie jest dokładnie tym - wszystko może się zdarzyć, od niczego, po awarię programu lub systemu operacyjnego. Nawet dobrze zdefiniowane zachowanie może być złośliwe. Jeśli mogę zmienić x
wskaźnik na mój program i sprawić, by Twój program używał go jako wskaźnika, to mogę sprawić, że Twój program zacznie uruchamiać mój program - właśnie to robią hakerzy. Kompilator pomaga upewnić się, że nie używamy go int x
jakostring
i rzeczy tego rodzaju. Sam kod maszynowy nie jest świadomy typów i robi tylko to, co nakazują instrukcje. Istnieje również duża ilość informacji odkrytych w czasie wykonywania: z których bajtów pamięci może korzystać program? Czy x
zaczyna się od pierwszego bajtu, czy od 12?
Ale możesz sobie wyobrazić, jak okropnie byłoby pisać takie programy (i możesz to zrobić w języku asemblera). Zaczynasz od „zadeklarowania” zmiennych - mówisz sobie, że bajt 1 to x
bajt 2 y
, a kiedy piszesz każdy wiersz kodu, ładując i przechowując rejestry, musisz (jako człowiek) pamiętać, który jest, x
a który jeden jest y
, ponieważ system nie ma pojęcia. A ty (jako człowiek) musisz pamiętać, jakie typy x
i jakie y
są, ponieważ znowu - system nie ma pojęcia.