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:

printf

https://chacker.pl/

Język C zawiera wiele przydatnych konstrukcji dołączonych do biblioteki libc. Jedną z wielu powszechnie używanych konstrukcji jest polecenie printf, zwykle używane do wyświetlania wyników na ekranie. Istnieją dwie formy polecenia printf:

printf(<string>);

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

Pierwszy format jest prosty i służy do wyświetlania prostego ciągu znaków na ekranie. Drugi format zapewnia większą elastyczność dzięki zastosowaniu typu formatu, który może składać się ze zwykłych znaków i symboli specjalnych, które pełnią funkcję obiektów zastępczych dla listy zmiennych występujących po przecinku. Często używane symbole formatów są wymienione i opisane w Tabeli  

Te typy formatów pozwalają programiście wskazać, w jaki sposób dane mają być wyświetlane na ekranie, zapisywane do pliku lub w inny sposób poprzez użycie rodziny funkcji printf. Załóżmy na przykład, że wiesz, że zmienna jest zmiennoprzecinkowa i chcesz mieć pewność, że zostanie  wydrukowana jako taka, a także chcesz ograniczyć jej szerokość, zarówno przed, jak i za zmiennoprzecinkową. W takim przypadku możesz użyć kodu w poniższym laboratorium w Kali, gdzie najpierw zmieniamy naszą powłokę na bash, a następnie pobieramy kod z GitHub za pomocą git clone.

Zmienne

https://chacker.pl/

Zmienne są używane w programach do przechowywania fragmentów informacji, które mogą się zmieniać i mogą być wykorzystywane do dynamicznego wpływania na program. Tabela  przedstawia niektóre popularne typy zmiennych.

Kiedy program jest kompilowany, większość zmiennych ma wstępnie przydzieloną pamięć o stałym rozmiarze, zgodnie z definicją rozmiaru specyficzną dla systemu. Rozmiary podane w Tabeli  są uważane za typowe; nie ma gwarancji, że otrzymasz dokładnie te rozmiary. Zdefiniowanie rozmiaru zależy od implementacji sprzętowej. Jednakże funkcja sizeof() jest używana w C, aby zapewnić, że kompilator przydzieli odpowiednie rozmiary. Zmienne są zwykle definiowane w górnej części bloku kodu. Gdy kompilator przeżuwa kod i buduje tablicę symboli, musi znać zmienną, zanim zostanie ona później użyta w kodzie. Słowo „symbol” to po prostu nazwa lub identyfikator. Ta formalna deklaracja zmiennych odbywa się w następujący sposób:

Na przykład w wierszu

int a = 0;

w pamięci deklarowana jest liczba całkowita (zwykle 4 bajty) z symbolem a i wartością początkową 0. Po zadeklarowaniu zmiennej konstrukcja przypisania służy do zmiany wartości zmiennej. Na przykład oświadczenie

x=x+1;

jest instrukcją przypisania, która zmienia wartość zmiennej x. Nowa wartość x jest bieżącą wartością x zmodyfikowaną przez operator +. Powszechnie używa się formatu

miejsce docelowe = źródło <z opcjonalnymi operatorami>

gdzie miejsce docelowe to lokalizacja, w której przechowywany jest wynik końcowy.

Funkcje

https://chacker.pl

Funkcje to samodzielne pakiety kodu, które można wywołać do wykonania za pomocą funkcji main() lub innych funkcji. Są one nietrwałe i można je wywoływać tyle razy, ile potrzeba, co zapobiega konieczności powtarzania tego samego kodu w całym programie. Format jest następujący:

Nazwa funkcji i opcjonalna lista argumentów składają się na podpis. Patrząc na to, możesz stwierdzić, czy funkcja wymaga argumentów, które zostaną użyte w przetwarzaniu procedur funkcji. Zwróć także uwagę na opcjonalną wartość zwracaną; informuje Cię, czy funkcja zwraca wartość po wykonaniu, a jeśli tak, to jakiego typu są to dane. Wywołanie funkcji może wyglądać następująco:

Poniżej znajduje się prosty przykład:

Tutaj dołączamy odpowiednie pliki nagłówkowe, które obejmują deklaracje funkcji dla wyjścia i printf. Funkcja wyjścia (1) jest zdefiniowana w stdlib.h, a printf (2) jest zdefiniowana w stdio.h. Jeśli nie wiesz, jakie pliki nagłówkowe są wymagane w oparciu o dynamicznie połączone funkcje, których używasz w programie, możesz po prostu zajrzeć do ręcznego wpisu, takiego jak man sscanf, i zapoznać się ze streszczeniem na górze. Następnie definiujemy funkcję main (3) z wartością zwracaną int. Podajemy void (4) w miejscu argumentów pomiędzy nawiasami, ponieważ nie chcemy zezwalać na przekazywanie argumentów do funkcji głównej. Następnie tworzymy zmienną o nazwie x z typem danych int (5) . Następnie wywołujemy funkcję foo (6) i przypisujemy wartość zwracaną do x. Funkcja foo po prostu zwraca wartość 8 (7). Wartość ta jest następnie drukowana na ekranie za pomocą funkcji printf, używając ciągu formatującego %d, aby traktować x jako wartość dziesiętną (8).  Wywołania funkcji modyfikują przebieg programu. Po wywołaniu funkcji wykonanie programu tymczasowo przechodzi do funkcji. Po zakończeniu wykonywania wywoływanej funkcji sterowanie powraca do funkcji wywołującej pod adresem pamięci wirtualnej bezpośrednio pod instrukcją wywołania.

Podstawowe konstrukcje języka C

https://chacker.pl/

Chociaż każdy program w języku C jest wyjątkowy, w większości programów można znaleźć pewne wspólne struktury. Omówimy je w kilku następnych sekcjach. main() Wszystkie programy w C „powinny” (zobacz wyjątek w sekcji „Dalsza lektura”) zawierają funkcję main() (małe litery) zgodną z formatem

gdzie zarówno typ wartości zwracanej, jak i argumenty są opcjonalne. Jeśli nie określono typu wartości zwracanej, używany jest typ zwracany int; jednakże niektóre kompilatory mogą generować ostrzeżenia, jeśli nie określisz zwracanej wartości jako int lub spróbujesz użyć void. Jeśli używasz argumentów wiersza poleceń dla funkcji main(), możesz użyć formatu

(między innymi), gdzie liczba całkowita argc przechowuje liczbę argumentów, a tablica argv przechowuje argumenty wejściowe (łańcuchy). Nazwa programu jest zawsze przechowywana pod offsetem argv[0]. Nawiasy i nawiasy są obowiązkowe. Nawiasy służą do oznaczenia początku i końca bloku kodu. Chociaż wywołania procedur i funkcji są opcjonalne, bez nich program nic by nie zrobił. Instrukcja procedury to po prostu seria poleceń, które wykonują operacje na danych lub zmiennych i zwykle kończą się średnikiem.