Week 01

umiestnenie údajov v pamäti, operátory referencie a dereferencie

Course Introduction

  • (slide) Predmet sa volá Programovanie a naším cieľom je z vás vychovať programátorov. Slovo programovanie vyjadruje činnosť a preto bude aj tento kurz veľmi praktický.

  • Motivačne: na konci kurzu sa budete môcť pochváliť svojou (možno) druhou (alebo už treťou?) hrou, ktorú vytvoríte (keďže Hangmana a nejakú prvotinu v Scratch-i ste spáchali už v rámci predmetu Základy algoritmizácie a programovania)

  • (slide) Stránka celého predmetu sa nachádza na http://it4kt.cnl.sk/c/pvjc - tu nájdete cvičenia, prednášky, príklady použité na prednáškach aj všetko potrebné pre úspešné absolvovanie tohto kurzu.

  • (slide) Pre komunikáciu v rámci predmetu budeme používať Mattermost. Ten už poznáte z predmetu ZAP, takže ho veľmi netreba predstavovať.

  • (slide) Ak hľadáte literatúru pre tento predmet, môžete použiť titul Učebnice jazyka C od Pavla Herouta (slide) alebo Programovací jazyk C od tvorcov jazyka C Kernighan-a a Ritchie-ho (slide).

  • (slide) Ďalšie odporúčanie pre zvládnutie tohto predmetu je portál codewars. Jedná sa o jeden z mnohých portálov/stránok, ktoré sa dnes snažia popularizovať progarmovanie herným spôsobom. Máte k dispozícii riešenie podobné našej Aréne, kde môžete voľne riešiť rozličné úlohy, za ktoré dostávate body a samozrejme s vyšším počtom bodov stúpate v rebríčku hodnotení. Veľmi dobrý spôsob, ako začať deň s cvičením v jazyku C na stránke codewars.

  • Tento predmet nekončí skúškou, ale klasifikovaným zápočtom. Môžete teda za neho získať spolu 100 bodov (slide), ktoré získate vypracovaním niekoľkých zadaní. Našou snahou je, aby tých zadaní bolo čo najviac, aj keď tohto roku skončíme pri čísle 5. Aj preto pristupujte ku každému jednému zadaniu a k jeho riešeniu čo najsvedomitejšie.

  • Hodnotenie každého jedného zadania bude rovnaké, takže sa nespoliehajte na to, že stačí zamakať až na poslednom zadaní, lebo zaň získate najviac bodov. Tie najjednoduchšie zadania zo začiatku budú stáť rovnaký počet bodov ako tie najťažšie na konci.

  • Upozorňujem však, že zadania budú ťažké, lebo len vtedy sa niečo naučíte. Ak by boli ľahké, tak sa vlastne so svojimi schopnosťami nikam nepohnete. Síce si môžeme naivne myslieť, že vtedy stagnujeme, ale opak je pravdou - tým, že sa neučíme nové veci, tak sa nerozvíjame a naše schopnosti sa strácajú.

  • Jediné zadanie, ktoré budete odovzdávať osobne, bude posledné - Arduino Project. Vtedy sa okrem prezentácie riešenia budeme baviť aj o jeho podrobnostiach. Podotýkam dopredu, že pokiaľ lektor, ktorý bude vaše zadanie preberať, nadobudne pochybnosti o jeho autorstve, môžete skončiť s neudeleným zápočtom za predmet.

  • Ak ste opakujúci študent, musíte predmet absolvovať s aktuálnymi podmienkami. Nemusíte síce chodiť na prednášky a cvičenia, keďže ste ich absolvovali vlani, ale musíte ako ostatní získať minimálne 51% z vypracovaných projektov. O podmienkach bližšie - viď info stránka predmetu (slide).

  • Pri absolvovaní predmetu budeme dbať na to, aby ste postupovali podľa etického kódexu (slide) a to hlavne pri práci na vašich zadaniach. Pravidelne vás naň budeme upozorňovať. Aplikujeme totiž nulovú toleranciu voči plagiátom. Síce politicky vraj slovo plagiát nepoznáme, ale máme s ním bohaté niekoľkoročné skúsenosti. Na kapitána teda tento predmet nedáte.

  • V závere semestra sa pozrieme na úvod do programovania mikrokontroléra Arduino (slide).

  • Na stránke nájdete aj zoznam komponentov, ktoré si kúpte. Ak chcete ušetriť, odporúčame vám minimálne samotný mikrokontrolér spolu s prepojovacím (kontaktným) poľom objednávať z Číny už teraz. Ostatné súčiastky si už môžete kúpiť aj v lokálnych rádioamatérskych obchodoch. Po dobrej spolupráci z minulého roku sa aj tento rok pokúsime pripraviť hotové balíčky, ktoré si prídete len kúpiť.

  • Ak hľadáte literatúru pre programovanie mikrokontroléra Arduino v domácom jazyku, siahnite po knižke Arduino - Uživatelská příručka (slide).

  • V tejto súvislosti sa chceme aj tento rok zúčastniť celosvetého dňa s názvom Arduino Day (slide). Tento rok pripadá termín na marec, my však budeme organizovať akciu s názvom Namakaný deň 10.apr.2019. Je to streda a chcem, aby sa akcie zúčastnilo čo najviac z vás. Jednu stredu teda prednáška a cvičenia nebudú, ale miestno nej budete mať možnosť sa zúčastniť prednášok, workshopov a sprievodných aktivít počas tejto akcie.

  • A môžeme začať…

Excercise

  • (slide) …a vlastne ešte pred začiatkom si overím vaše programátorské skúsenosti, keďže už vraj programovať viete. Mám tu pripravený fragment jednoduchého kódu, ktorý by ste mali zvládnuť vykonať.

  • Takže pripravení? Poďme na to.

Introduction

  • (slide)

  • Dnešná téma, ktorej sa budeme na prednáške venovať, je pomerne kľúčová, čo sa týka programovania v jazyku C, preto s ňou začíname ako prvou. Rovnako tak bude dosť náročn��, pretože sa budeme rozprávať o pamäti, jej prideľovaní a uvoľňovaní a o tom, kde a ako sa budú údaje ukladať.

  • Táto téma je špecifická pre jazyk C, pretože pokiaľ budete pracovať v niektorom vyššom programovacom jazyku, ako napr. Java, C# alebo Python, tak sa s dynamickou a vlastnou správou pamäte nestretnete.

Funkction swap()

Under the Hood

  • Aby sme lepšie videli, čo sa deje pod povrchom, použijeme debugger cgdb. Nastavíme breakpoint na funkciu main():

    a následne program spustíme príkazom run, ktorý sa preruší na začiatku funkcie main().

  • V debuggeri cgdb môžeme využiť príkaz print na vypísanie obsahu premennej v tvare print var.

  • Ak teda spustíme kód programu zavolaním príkazu run zistíme a budeme ho postupne krokovať príkazom step tak zistíme, že funkcia swap() funguje presne tak, ako fungovať má. To znamená, že pred jej ukončením naozaj dôjde k zámene oboch premenných a aj b.

  • Problém však nastane v momente, keď sa vykonávanie vráti späť do funkcie main(). Tu sa v oboch premenných budú nachádzať ich pôvodné hodnoty bez akejkoľvek zámeny. Prečo teda došlo k zámene hodnôt len vo vnútri funkcie swap()? Prečo výsledok nepretrval aj po návrate späť do funkcie main()?

Odovzdávanie parametrov funkcii kópiou

  • (slide) Pozrime sa na to, na akých adresách sa naše premenné nachádzajú. Na to, aby sme sa túto informáciu dozvedeli, použijeme operátor referencie &. Príkazom

    teda vypíšeme na obrazovku adresy oboch premenných a aj b.

  • Tu si môžeme všimnúť aj skutočnosť, že ak vypíšeme adresy oboch premenných na obrazovku, rozdiel medzi nimi budú práve 4 byty. Tieto premenné sú totiž v pamäti umiestnené hneď za sebou a keďže sa jedná o hodnoty typu integer, ich veľkosť v pamäti sú práve 4 byty.

    Poznámka

    To samozrejme nemusí byť pravda vždy. Veľkosť hodnoty typu integer v pamäti na svojej platforme (vo vašom systéme) sa dozviete pomocou operátora

    
           +----+
    0xd108 | 20 |
           +----+
    0xd10c | 10 |
           +----+
  • Tu si však všimnime problém, ktorý nastane po vstúpení do funkcie swap() - ak sa pozrieme na adresy parametrov funkcie a a b, zistíme, že sú iné, ako tie priamo vo funkcii main().

  • Ako je možné, že adresy parametrov funkcie swap() sú iné ako tie, ktoré sme jej odovzdali pri jej volaní?

  • Táto skutočnosť je spôsobená tým, že parametre boli funkcii odovzdané kópiou. To znamená, že sa vytvorila kópia ich obsahu v pamäti a funkcia swap() dostala pri volaní práve adresy týchto skopírovaných parametrov. Akékoľvek operácie s týmito premennými sa teda robili s kópiami a nie s originálmi týchto premenných.

Odovzdávanie parametrov funkcii adresou

  • Aby sme tento problém vyriešili, nemôžeme pracovať s kópiou hodnôt premenných, ale potrebovali by sme do funkcie posunúť adresy pôvodných premenných z funkcie main().

  • Jazyk C totiž umožňuje odovzdávať parametre funkciám oboma možnými spôsobmi:

    • hodnotou
    • adresou
  • Ak teda chceme zabezpečiť odovzdanie adresy premennej, pri jej volaní použijeme operátor &:

  • Ak sa však pokúsime preložiť program, skončíme s nasledovným hlásením prekladača:

    error: incompatible pointer to integer conversion passing 'int *' to parameter of type 'int'; remove &
  • (slide) Samozrejme, aby všetko fungovalo správne, potrebujeme upraviť aj samotnú funkciu swap(). Jej parametre tentokrát dostanú adresu, ktorú sme funkcii poslali pomocou operátora referencie &. Aby sme túto adresu mohli vo funkcii prijať, jej parametre budú typu ukazovateľ na typ int. Za týmto účelom použijeme operátor dereferencie *. Signatúra funkcie swap() bude vyzerať nasledovne:

    Poznámka

    Operátor dereferencie má niekoľko ďalších názvov. Napríklad sa môžete stretnúť s názvom go-to operator alebo value-of operator, pretože pomocou tohto operátora je možné prejsť na obsah príslušnej adresy. Alebo sa dá často stretnúť s názvom smerník, pretože smeruje na konkrétnu adresu v pamäti.

  • Telo funkcie swap() zakomentujeme a necháme ho prázdne. Program teda bude aktuálne vyzerať nasledovne:

  • Opäť preskúmame, čo sa v skutočnosti stalo pomocou nástroja na ladenie cgdb. Nastavíme breakpoint na funkciu main() a pomocou príkazu step začneme krokovať program až do funkcie swap().

  • Tu si môžeme všimnúť, že:

    • vypísaním obsahu parametra a sa zobrazí adresa, na ktorej sa nachádza hodnota premennej a z funkcie main()

    • vypísaním adresy parametra a sa zobrazí adresa, na ktorej sa nachádza uložená adresa pôvodnej premennej a z funkcie main()

    • k hodnote, na ktorú ukazuje adresa uložená v parametri a, sa dostaneme pomocou operátora dereferencie

    Poznámka

    Jeden smerník zaberá v pamäti práve 8B a je jedno, o aký údajový typ sa jedná.

  • Zostáva nám teda už len vykonať potrebné úravy vo funkcii swap():

  • Po preklade a spustení konečne dôjde k výmene obsahu oboch premenných.

Input, Output and Input-Output Types of Function Parameters

  • todo - dopísať

  • Hovorili sme o tom, že ak chceme vyriešiť problém s výmenou hodnôt dvoch premenných pomocou funkcie, musíme na to použiť vstupno-výstupné parametre a parameter samotný potrebujeme tejto funkcii odovzdať adresou a nie hodnotou.

    Poznámka

    Ak nás bude zaujímať konkrétna hodnota (adresa) a budeme ju chcieť vypísať na obrazovku, ako znak konverzie vo formátovacom reťazci funkcie printf() použijeme p.

String Compare

  • Pozrieme sa na ďalší problém - porovnanie dvoch reťazcov. Vychádzajme z predpokladu, že ak pomocou operátora == vieme porovnať dve ľubovoľné hodnoty dvoch ľubovoľných základných typov, vieme rovnakým operátorom porovnávať navzájom aj reťazce (compare-1.c):

    Upozornenie

    Pokiaľ tento kód preložíme pomocou príkazu make, pričom budú nastavené všetky prepínače prekladača, ktoré sa nachádzajú v premennej CFLAGS a používajú sa pre potreby predmetu, prekladač zahlási chybu:

    error: array comparison always evaluates to false

    Pre ilustrovanie preberaného problému preto program stačí preložiť len samotným prekladačom gcc bez parametrov.

  • Ak tento program spustíme, miesto očakávanej správy Equal dostaneme správu Differ. Prečo?

  • Vieme už, že premenná, ktorá je typu “reťazec”, je vlastne adresou reťazca umiestneného v pamäti. To znamená, že operátor == teda nebude porovnávať obsahy reťazcov, ale dve adresy navzájom. Ak sú tieto adresy zhodné, tak aj “reťazce sú zhodné”.

  • Túto skutočnosť si opäť vieme overiť pomocou nástroja cgdb. Ak však napíšeme priamo príkaz print str1, zistí z lokálneho kontextu, že sa jedná o jednorozmerné pole a vypíše priamo jeho obsah. Adresu reťazca preto získame zadaním príkazu print &str1.

  • Ak nás naopak bude zaujímať obsah, ktorý sa na danej adrese nachádza, použijeme operátor * v tvare: print *str1. Uvidíme však, že na danej adrese sa nachádza len jedno písmeno, čo znamená, že adresa poľa je vlastne adresou prvého prvku daného poľa.

Smerníková aritmetika

  • Týmto spôsobom sa vieme dostať aj priamo na jednotlivé prvky poľa:

    
    print *(str1 + 2)

String Literal

The Solution

Conclusion

Additional Resources