proszę o ocenę tutoriala - funkcje w języku C

0

cześć, piszę mały tutorial na temat funkcji w C dla znajomych z polibudy. czy powinienem zamieścić jeszcze coś, żeby pomóc im zrozumieć funkcje?

Podstawy programowania

Programowanie to pisanie instrukcji dla procesorów. Procesory wykonują instrukcje linijka po linijce, z góry do dołu.

Wyrażenia

Wyrażenia w językach programowania to wszystko co zwraca wartość. Mogą to być wyrażenia matematyczne, lub wywołania funkcji. Funkcje, które zwracają void też są wyrażeniami, ale używanie wartości wyrażenia ich wywołania jest błędem i uniemożliwi kompilacje programu w językach C i pochodnych.

Funkcje (podejście matematyczne)

Żeby zrozumieć funkcje trzeba podejść do nich z dwóch stron: należy zrozumieć matematyczne pojęcie funkcji oraz sposób, w jaki wykonywane są przez procesor komputera.

Ze strony matematycznej funkcja to przyporządkowanie pewnym argumentom wartości. Wywołując funkcję z pewnymi argumentami otrzymamy pewną wartość. W matematyce wywoływanie funkcji z danymi argumentami dowolną ilość razy zawsze da ten sam wynik. Funkcje, którę będziemy pisać nie są matematyczne i nie obowiązuje ich ta zasada. Funkcje, które mimo tego trzymają się tej reguły nazywamy czystymi.

Wyrażenie wywołania czystej funkcji jest równe wynikowi tej funkcji, czyli wyrażenie 2 + 2; jest równe wyrażeniu dodaj(2, 2); i jest równe wyrażeniu 4;. Przy definicji funkcji dodaj:

int dodaj(int a, int b) { return a + b; }
// funkcja czysta bo do ustalenia wyniku korzysta tylko z argumentów

Czyste funkcje pozwalają nie myśleć o sobie jak o podprogramie, który wykonuje polecenia, które trzeba znać bo mogą mieć konsekwencje w dalszym działaniu programu. Dzięki temu, że funkcja ta nie może mieć wpływu na stan programu można myśleć o jej wywołaniu jak o statycznym wyrażeniu. W matematyce, mówiąc o sinusie z 30 stopni nie myślimy o procedurze, jaką należałoby przeprowadzić, tylko o wyniku - liczbie 0,5. Dlatego, mówiąc o wyrażeniu dodaj(2, 2) nie należy myśleć o procedurze dodawania wykonywanej podczas wywołania funkcji, lecz o jej wyniku jak o wartości stałej - liczbie 4. Matematyczne wywołanie funkcji to jak przeczytanie wyniku dla odpowiednich argumentów z wydrukowanej tabeli. Wynik będzie zawsze taki sam dla takich samych argumentów i przeczytanie go z tabeli nie będzie miało żadnych skutków ubocznych jak zmiana jakiejś innej wartości w tabeli.

Stos

Zrozumienie funkcji od strony sprzętowej wymaga rozumienia pojęcia stosu. Stos jako struktura danych pozwala na dodawanie danych i zdejmowanie ich. Ważna jest kolejność zdejmowania. Zdjąć ze stosu można tylko dane umieszczone na nim najpóźniej (znajdujące się na jego szczycie). Żeby usunąć podstawę stosu trzeba po kolei zdejmować wszystkie wartości umieszczone na stosie po umieszczeniu podstawy.

Sprzętowo, stos to pewna ilość zaalokowanej pamięci RAM. Każdy proces uruchomiony w komputerze dostaje swój stos. W procesorze komputera znajduje się rejestr przechowywujący liczbę całkowitą - adres bitu pamięci ram, który jest w danym momencie szczytem stosu; nazywa się go wskaźnikiem stosu.

Wywołanie funkcji (sprzętowo)

Sprzętowa implementacja funkcji jest mniej abstrakcyjna i można podzielić ją na kilka etapów

  1. Przesunięcie wskaźnika stosu, tak aby zmieściły sie pod nim wartości wszystkich argumentów funkcji i jej zmiennych lokalnych

  2. Wykonanie operacji z definicji funkcji

  3. Opcjonalne przepisanie wyrażenia po słowie kluczowym return w odpowiednie miejsce w pamięci

  4. Przesunięcie wskaźnika stosu w miejsce, w którym był przed wykonaniem kroku 1.

Zasięg zmiennych

W językach programowania zasięg zmiennych to części kodu, w których można korzystać z określonych zmiennych. Zmienne globalne mają zasięg w każdym miejscu programu. Zmienne lokalne są w zasięgu tylko w bloku kodu, w którym zostały zdefiniowane. W języku C++ bloki kodu są określone klamrami. Przy rozpoczęciu bloku kodu na stos zostaje włożony nowy segment, który będzie przechowywał zmienne w tym bloku. Po wyjściu programu z bloku kodu, wskaźnik stosu zostaje cofnięty co powoduje usunięcie wszystkich zmiennych w opuszczonym bloku.

Zmienne zdefiniowane w bloku kodu znajdującym się w innym bloku mają dostęp do zmiennych ze wszystkich nadrzędnych bloków.

int a;
void func() {
    int b;
    {
        int c;
        {
            int d;
            cout << a + b + c + d << endl; // Prawidłowe wyrażenie
        } // <- tu zmienna d zostaje skasowana
    } // <- tu zmienna c zostaje skasowana
} // <- tu zmienna b zostaje skasowana
1

W tytule i opisie widzę C w kodzie widzę C++!
Może warto by było, żebyś obejrzał to.

gbjk napisał(a):

Wywołanie funkcji (sprzętowo)

Sprzętowa implementacja funkcji

WAT? Chyba wiem o co ci chodziło, ale ująłeś to w takie słowa, że zajęło mi to chwilę.

0

Jak dla mnie to jest dośc niezrozumiałe:

  1. Przesunięcie wskaźnika stosu, tak aby zmieściły sie pod nim wartości wszystkich argumentów funkcji i jej zmiennych lokalnych

Nie trzeba przesuwać wskaźnika stosu, żeby zaalokować zmienne lokalne, bo kompilator zwykle używa do tego rejestru ebp/rbp bez przesunięcia esp/rsp. Generalnie można powiedzieć kompilatorowi, żeby używał esp/rsp poprzez flagę -fomit-frame-pointer, ale by default tego nie ma.
Co więcej do przekazywania argumentów funkcjom również nie zawsze używa się do tego stosu i jego wskaźnika. Przy architekturze x86 owszem, ale w x64 najczęściej stosowana konwencja wywołań oparta jest o rejestry -> https://stackoverflow.com/questions/2535989/what-are-the-calling-conventions-for-unix-linux-system-calls-on-i386-and-x86-6

0
gbjk napisał(a):

cześć, piszę mały tutorial na temat funkcji w C dla znajomych z polibudy. czy powinienem zamieścić jeszcze coś, żeby pomóc im zrozumieć funkcje?

Lepiej powiedz im, żeby poczytali K&R, bo do zrozumienia funkcji nie potrzeba schodzić głęboko, a mieszanie początkującym w głowie nie sprzyja nauce.

0
Shizzer napisał(a):

Jak dla mnie to jest dośc niezrozumiałe:

  1. Przesunięcie wskaźnika stosu, tak aby zmieściły sie pod nim wartości wszystkich argumentów funkcji i jej zmiennych lokalnych

Nie trzeba przesuwać wskaźnika stosu, żeby zaalokować zmienne lokalne, bo kompilator zwykle używa do tego rejestru ebp/rbp bez przesunięcia esp/rsp. Generalnie można powiedzieć kompilatorowi, żeby używał esp/rsp poprzez flagę -fomit-frame-pointer, ale by default tego nie ma.
Co więcej do przekazywania argumentów funkcjom również nie zawsze używa się do tego stosu i jego wskaźnika. Przy architekturze x86 owszem, ale w x64 najczęściej stosowana konwencja wywołań oparta jest o rejestry -> https://stackoverflow.com/questions/2535989/what-are-the-calling-conventions-for-unix-linux-system-calls-on-i386-and-x86-6

Dzięki za to bo nie wiedziałem, ale moim celem jest wytłumaczenie działania funkcji w językach C/C++ i wydaje mi się że wytłumaczenie ze wskaźnikiem stosu to dość pożyteczne uogólnienie. Z architektury systemów komputerowych mieliśmy tylko x86 więc nie chcę wprowadzać innych procesorów.

0

Fajnie, że próbujesz to wytłumaczyć też nisko poziomowo, ale:

  1. Dla początkujących jest to zbędne, bo tylko zaśmiecają sobie tym głowę na początku. Dla średnio-zaawansowanych to już może przejść.
  2. Architektura x86 powoli usuwa się w cień, a x64 przejmuje kontrolę także jeśli już piszesz o tym w tutorialu to bardziej kieruj uczniów w stronę x64. ;)
0
Shizzer napisał(a):
  1. Dla początkujących jest to zbędne, bo tylko zaśmiecają sobie tym głowę na początku. Dla średnio-zaawansowanych to już może przejść.

Napisałem to bo zauważyłem, że mają problem ze zrozumieniem, dlaczego nie mogą zwrócić wskaźnika do zmiennej lokalnej. To wytłumaczenie stosu przydaje się też do zrozumienia rekurencji. Samo wytłumaczenie matematyczne nie wystarczyłoby do zrozumienia np. przepełnienia stosu czy optymalizacji rekurencji ogonowej

0

Dobrze rozumiem, ze mieliscie zajecia z architektur ale koledzy nie rozumieja funkcji? Tzn. myslalem ze to jakis nieinformatyczny kierunek i stad trzeba takie rzeczy tlumaczyc a tu zonk

0

Napisałem to bo zauważyłem, że mają problem ze zrozumieniem, dlaczego nie mogą zwrócić wskaźnika do zmiennej lokalnej. To wytłumaczenie stosu przydaje się też do zrozumienia rekurencji. Samo wytłumaczenie matematyczne nie wystarczyłoby do zrozumienia np. przepełnienia stosu czy optymalizacji rekurencji ogonowej

W takim razie jest to zrozumiałe jeśli chcieli wiedzieć dlaczego tak się dzieje. Ale wtedy powinieneś raczej wkleić jakieś screeny debuggera, żeby przekonali się jak sprawa wygląda w pamięci. Taka sucha teoria może być dość trudna do zrozumienia.

0

Jak widać nie jest prosto coś, napisać. A można też sięgnąć po gotowe rzeczy, ja bym im dał np., to:
http://cslibrary.stanford.edu/101/EssentialC.pdf

1 użytkowników online, w tym zalogowanych: 0, gości: 1