Pamięć komputera

https://chacker.pl/

Mówiąc najprościej, pamięć komputera to elektroniczny mechanizm, który ma zdolność przechowywania i odtwarzania danych. Najmniejsza ilość danych, jaką można zapisać, to 1 bit, który może być reprezentowany w pamięci przez 1 lub 0. Kiedy połączysz 4 bity, nazywa się to półbajtem i może reprezentować wartości od 0000 do –1111. Istnieje dokładnie 16 wartości binarnych, od 0 do 15, w formacie dziesiętnym. Kiedy połączysz razem dwa półbajty, czyli 8 bitów, otrzymasz bajt, który może reprezentować wartości od 0 do (28 – 1) lub od 0 do 255 w systemie dziesiętnym. Kiedy połączysz 2 bajty, otrzymasz słowo, które może reprezentować wartości od 0 do (216 – 1) lub od 0 do 65 535 w systemie dziesiętnym. Kontynuując łączenie danych, jeśli połączysz dwa słowa, otrzymasz słowo podwójne, czyli DWORD, które może reprezentować wartości od 0 do (232 – 1) lub od 0 do 4 294 967 295 w systemie dziesiętnym. Dwa DWORD razem to poczwórne słowo, czyli QWORD, które może reprezentować wartości od 0 do (264 – 1) lub od 0 do 18 446 744 073 709 551 615 w formacie dziesiętnym. Jeśli chodzi o adresowanie pamięci w 64-bitowych procesorach AMD i Intel, wykorzystywane jest tylko dolne 48 bitów, co daje 256 terabajtów adresowalnej pamięci. Jest to dobrze udokumentowane w niezliczonych zasobach internetowych. Istnieje wiele rodzajów pamięci komputerowej; skupimy się na pamięci o dostępie swobodnym (RAM) i rejestrach. Rejestry to specjalne formy pamięci wbudowane w procesory, które zostaną omówione w dalszej części.

Kompilacja za pomocą gcc

https://chacker.pl/

Kompilacja to proces przekształcania kodu źródłowego czytelnego dla człowieka w pliki binarne nadające się do odczytu maszynowego, które mogą zostać przetworzone przez komputer i wykonane. Mówiąc dokładniej, kompilator pobiera kod źródłowy i tłumaczy go na pośredni zestaw plików zwany kodem wynikowym. Pliki te są prawie gotowe do wykonania, ale mogą zawierać nierozwiązane odniesienia do symboli i funkcji, które nie są zawarte w oryginalnym pliku kodu źródłowego. Te symbole i odniesienia są rozpoznawane w procesie zwanym łączeniem, ponieważ każdy plik obiektowy jest łączony w wykonywalny plik binarny. Uprościliśmy dla Ciebie ten proces, ale oto główne kroki. Podczas programowania w języku C w systemach Unix większość programistów woli używać kompilatora GNU C (gcc). gcc oferuje wiele opcji podczas kompilacji. Najczęściej używane flagi wymieniono i opisano w tabeli

meet.c

https://chacker.pl/

A teraz coś nieco bardziej złożonego. Ten program pobierze dane wejściowe, zapisze je, a następnie wydrukuje:

Program ten pobiera dwa argumenty wiersza poleceń (1) i wywołuje funkcję greeting() (2), która wypisuje „Hello” i podaną nazwę, po której następuje powrót karetki (3). Kiedy funkcja greeting() zakończy się, sterowanie wraca do funkcji main(), która wypisuje „Bye” i podaną nazwę (4). Na koniec program kończy pracę (5).

Przykładowe programy

https://chacker.pl/

Możesz teraz przejrzeć swój pierwszy program.

 hello.c

Zaczniemy od pokazania programu z // komentarzami, a następnie omówimy program.

Ten bardzo prosty program wypisuje komunikat „Hello haxor!” na ekran za pomocą funkcji printf, zawartej w bibliotece stdio.h. Spróbuj go skompilować, skoro już wiesz, jak to zrobić, i uruchom go!

if/else

https://chacker.pl/

Konstrukcja if/else służy do wykonania serii instrukcji, jeśli spełniony zostanie określony warunek; w przeciwnym razie wykonywany jest opcjonalny blok instrukcji else. Jeśli nie ma bloku instrukcji else, działanie programu będzie kontynuowane po zakończeniu nawiasu zamykającego blok if (}). Poniżej znajduje się przykład konstrukcji if/else zagnieżdżonej w pętli for:

W tym przykładzie używamy pętli while(1) do przeglądania instrukcji if/else. Zanim wejdziemy w pętlę, ustawiamy zmienną x na 0. Ponieważ x jest równe 0, spełniamy warunek z instrukcji if (2). Następnie wywołujemy funkcję printf, zwiększamy x o 1 i kontynuujemy. Ponieważ x wynosi teraz 1, nie spełniamy warunku instrukcji if podczas drugiej iteracji w pętli. Dlatego przechodzimy do instrukcji else (3), która wywołuje funkcję printf, a następnie przerywa (4) pętlę. W przypadku pojedynczych instrukcji można pominąć nawiasy klamrowe.

Uwagi

Aby ułatwić czytelność i udostępnianie kodu źródłowego, programiści umieszczają w kodzie komentarze. Możesz użyć jednego z dwóch sposobów umieszczania komentarzy w kodzie: // lub /* i */. Typ komentarza // wskazuje, że wszelkie znaki znajdujące się w pozostałej części wiersza mają być traktowane jako komentarze i komputer nie może na nie reagować podczas wykonywania programu. Para /* i */ rozpoczyna i zatrzymuje blok komentarzy, który może obejmować wiele wierszy. W tym przypadku /* służy do rozpoczęcia komentarza, a */ służy do wskazania końca bloku komentarza.

Pętle

https://chacker.pl/

Pętle są używane w językach programowania do wielokrotnego wykonywania serii poleceń. Dwa popularne typy to pętle for i while. for pętle rozpocznij zliczanie od wartości początkowej, sprawdź wartość pod kątem jakiegoś warunku, wykonaj instrukcję i zwiększ wartość w następnej iteracji. Format jest następujący:

for(<wartość początkowa>; <wartość testowa>; <wartość zmiany>){

<instrukcja>;

}

Dlatego pętla for jak

for(i=0; i<10; i++){

printf(„%d”, i);

}

wypisze cyfry od 0 do 9 w tej samej linii (ponieważ \n nie jest używane), w ten sposób:

0123456789.

W przypadku pętli for warunek jest sprawdzany przed iteracją instrukcji w pętli, więc może się zdarzyć, że nawet pierwsza iteracja nie zostanie wykonana. Jeżeli warunek nie jest spełniony, wykonywanie programu po pętli jest kontynuowane.

UWAGA: Ważne jest, aby zwrócić uwagę na użycie operatora mniejszego niż (<) zamiast operatora mniejszego lub równego (<=), co pozwala na wykonanie pętli jeszcze raz, aż do i=10 . Jest to ważna koncepcja, która może prowadzić do błędów typu off-by-one. Zauważ też, że licznik zaczynał się od 0. Jest to powszechne w C i warto się do tego przyzwyczaić. Pętla while służy do iteracji po serii instrukcji, aż do spełnienia warunku. Podstawowy przykład jest następujący:

Pętle mogą być także zagnieżdżane jedna w drugiej.

strcpy/strncpy

https://chacker.pl/

Polecenie strcpy jest jedną z najniebezpieczniejszych funkcji używanych w C. Format polecenia jest następujący:

strcpy(<miejsce docelowe>, <źródło>);

Celem polecenia jest skopiowanie każdego znaku ciągu źródłowego (seria znaków kończących się znakiem null, \0) do ciągu docelowego. Jest to szczególnie niebezpieczne, ponieważ nie sprawdza się rozmiaru źródła przed jego skopiowaniem do miejsca docelowego. W rzeczywistości mówimy tutaj o nadpisywaniu lokalizacji w pamięci, co zostanie wyjaśnione w dalszej części tego rozdziału. Wystarczy powiedzieć, że gdy źródło jest większe niż miejsce przydzielone na miejsce docelowe, prawdopodobnie wystąpią warunki przepełnienia, co może skutkować kontrolą wykonywania programu. Przy właściwym użyciu bezpieczniejszą alternatywną funkcją jest polecenie strncpy. Tutaj jest format tego polecenia:

strncpy(<miejsce docelowe>, <źródło>, <szerokość>);

Pole <szerokość> służy do zapewnienia, że tylko określona liczba znaków zostanie skopiowana z ciągu źródłowego do ciągu docelowego, co pozwala programiście na większą kontrolę. Parametr szerokości powinien opierać się na rozmiarze miejsca docelowego, takim jak przydzielony bufor. Inną alternatywną funkcją z możliwością kontrolowania rozmiaru i obsługi błędów jest snprintf. Ogólnie rzecz biorąc, obsługa ciągów znaków w języku programowania C była zawsze przedmiotem dyskusji i szczegółowej analizy ze względu na wymagania programisty dotyczące alokacji pamięci.

UWAGA: Używanie nieograniczonych funkcji, takich jak strcpy, jest niebezpieczne; jednakże wiele tradycyjnych kursów programowania nie opisuje wystarczająco szczegółowo zagrożeń stwarzanych przez te funkcje. W rzeczywistości, gdyby programiści po prostu właściwie korzystali z bezpieczniejszych alternatyw, takich jak snprintf, wówczas cała klasa ataków związanych z przepełnieniem bufora byłaby mniej powszechna. Wielu programistów nadal korzysta z tych irytujących funkcji, ponieważ w dalszym ciągu często wykrywane są przepełnienia bufora. Kolejnym częstym problemem jest starszy kod zawierający nieprawidłowe funkcje. Na szczęście większość kompilatorów i systemów operacyjnych obsługuje różne zabezpieczenia łagodzące exploity, które pomagają zapobiegać wykorzystywaniu tego typu luk. To powiedziawszy, nawet funkcje ograniczone mogą ucierpieć z powodu nieprawidłowych obliczeń rozmiaru bufora.

scanf

https://chacker.pl/

Polecenie scanf uzupełnia polecenie printf i jest zwykle używane do uzyskiwania danych wejściowych od użytkownika. Format to

scanf(<ciąg formatujący>, <lista zmiennych/wartości>);

gdzie ciąg formatujący może zawierać symbole formatu, takie jak te pokazane dla printf w tabeli. Na przykład poniższy kod odczyta liczbę całkowitą od użytkownika i zapisze ją w zmiennej o nazwie liczba:

scanf(„%d”, &liczba);

Właściwie symbol & oznacza, że przechowujemy wartość w pamięci ,lokalizacja wskazana numerem. Będzie to miało większy sens, gdy będziemy mówić o wskaźnikach w dalszej części . Na razie pamiętaj, że musisz użyć symbolu & przed jakąkolwiek nazwą zmiennej za pomocą scanf. Polecenie jest na tyle inteligentne, że umożliwia zmianę typów w locie, więc jeśli wprowadzisz znak w poprzednim wierszu polecenia, polecenie automatycznie przekonwertuje znak na wartość dziesiętną (ASCII). Sprawdzanie granic nie jest jednak przeprowadzane w odniesieniu do rozmiaru łańcucha, co może prowadzić do problemów, co omówimy później.

Formatowanie ciągów znaków

https://chacker.pl/

Pobieramy kod wszystkich ćwiczeń z tej części, a następnie skupiamy się na ciągach formatujących, które pozwolą nam sformatować dane wyjściowe naszego programu według własnego uznania.

W pierwszym wywołaniu printf (1) używamy całkowitej szerokości 5, z 2 wartościami po zmiennoprzecinkowym. W drugim wywołaniu printf(2) używamy całkowitej szerokości 4, z 1 wartością po zmiennoprzecinkowym. Teraz skompilujmy go za pomocą gcc i uruchommy: