Manipulacija pomnilnika (sprotna naloga)
V sklopu te naloge demonstrirajte uporabo kazalcev in struktur za interpretacijo in manipulacijo podatkov neobičajnih oblik. Pripravite 3 rešitve (2 programa in eno implementacijo funkcije) v nadaljevanju opisanih nalog. Pripravite tudi Makefile, ki bo vašo kodo prevedel v izvedljive programe.
Posamezni naslovi podnalog v nadaljevanu so sestavljeni iz pričakovanega imena programa in pričakovanega imena izvorne zbirke. Na sistem oddajte .zip paket z Makefile skripto in izvornimi zbirkami. Bodite pozorni na poimenovanja. Pred oddajo preverite delovanje z avtomatskim preizkusom. naloga_1 (naloga_1.c)
Program naj s standardnega vhoda bere racionalna števila (ulomke) v nadaljevanju opisani obliki. Na standardni izhod naj izpisuje ulomke v berljivi obliki <števec>/. Primer izpisa programa:
3/4 -1/2 6/14
Racionalna števila bodo zapisana v posameznih zlogih/bajtih. Vsak zlog bo razdeljen na dva dela. Pomembnejši štirje biti (vrednosti 128, 64, 32 in 16) bodo vsebovali števec ulomka, zapisan kot predznačeno celo število (vrednosti med -8 in 7). Manj pomembni štirje biti (vrednosti 8, 4, 2 in 1) bodo vsebovali imenovalec ulomka, zapisan kot nepredznačeno celo število (vrednosti med 0 in 15).
Biti v enem zlogu predstavljajo posamezne vrednost. Naslednja tabela prikazuje nekaj primerov: vrednost bitna predstava a b c d e f g h 128 1 0 0 0 0 0 0 0 64 0 1 0 0 0 0 0 0 32 0 0 1 0 0 0 0 0 16 0 0 0 1 0 0 0 0 8 0 0 0 0 1 0 0 0 4 0 0 0 0 0 1 0 0 2 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 71=64+4+2+1 0 1 0 0 0 1 1 1 255=... 1 1 1 1 1 1 1 1
Vrednost 128 je v enem zlogu predstavljena z zadnjim bitom a, ki ga smatramo tudi kot najpomemnejši ker predstavlja največjo vrednost. Vrednost 2 pa je predstavljena z 2 bitom g. Ostala nepredznačena cela števila so predstavljena s kombinacijo bitov oziroma seštevkom njihovih vrednosti. Število 71 je tako predstavljeno z zlogom 01000111, oziroma seštevkom 64+4+2+1. Število 255 je kombinacija vseh bitov.
Po zgornjem opisu želimo v bite a b c d zapisati števec, v bite e f g h pa imenovalec.
Iz standardnega vhoda mora prebrati vse podane zloge. naloga_2 (naloga_2.c)
Program naj iz standardnega vhoda bere pare vrednosti - števce in imenovalce ulomkov. Na standardni izhod naj izpiše racionalna števila v obliki opisani za naloga_1. Program naj bo kompatibilen z naloga_1.
Za branje lahko uporabite funkcijo scanf. Z njo lahko preberete posamezno število s klicem:
int v; scanf("%d", &v);
Primer zagona (v kombinaciji z naloga_1):
$ echo -n 3 4 -1 2 6 14 | ./naloga_2 | ./naloga_1 3/4 -1/2 6/14
Velikost izhoda mora biti kot predpisana. V zgornjem primeru, kjer so podani 3 pari imenovalcev in števcev mora izhod biti velik 3 zloge/bajte. naloga_3.h
V tej nalogi implementirajte funkcijo v zaglavju naloga_3.h.
Implementirajte funkcijo split, ki C niz razreže na podnize ločene z izbranim znakom. Funkcija naj ima naslednji podpis:
char** split(const char* str, char delim, int *n);
Kjer je str niz, ki ga želimo razrezati, delim je znak s katerim nize razrežemo, n pa je kazalec na celo število, kamor bomo zapisali število najdenih nizov. Funkcija vrne kazalec na tabelo razrezanih podnizov. Vrnjena tabela je dinamično alocirana in jo sprostimo z enim klicem free. Vaša implementacija bo preizkušena z naslednjim programom, drugačnimi parametri:
#include <stdio.h> #include "naloga_3.h"
int main(){ int n=0; char *str="hello world !!!"; char delim=' '; printf("vhodni niz: %s\n", str); printf("separator: %c\n", delim);
char **besede = split(str, delim, &n);
printf("besede:\n");
for(int i=0; i<n; i+=1){
printf("%s\n", besede[i]);
}
free(besede);
}
Primer bi naj izpisal:
vhodni niz: hello world !!!
separator:
besede:
hello
world
!!!
Predlagamo naslednjo implementacijo (možne so razlike v implementaciji, dokler je izpolnjen zgoraj opisan način uporabe).
Najprej preštejte število ponovitev simbola delim v podanem nizu str. To število povečate za 1 in shranite na lokacijo kazalca n (število podnizov je 1 več kot število pojavitev simbola).
Nato alocirajte pomnilnik, ki bo lahko vseboval tabelo n-tih kazalcev char* in celotno dolžino podanega niza (to morate opraviti z enkratnim klicem malloc za katerega uporabnik nato kliče free). Na začetek tega pomnilnika shranite tabelo kazalcev char* (tabela je tipa char**). Na konec te tabele pa shranite kopijo niza.
Prvi kazalec v tabeli incializirate tako, da kaže na začetek kopije niza. Nato se sprehodite skozi znake kopije. Kjer najdete simbol delim ga zamenjate z simbolom '0' (tako zaključite prejšnji odsek kopije). Naslednji kazalec v tabeli nastavite na naslov za zamenjanim elementom.
Primer zapisa v pomnilniku na sistemu, kjer je kazalec velik 1 zlog/bajt in malloc vrne dinamično alociran pomnilnik na naslovu 10: naslov 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 znak h e l l o w o r l d ! ! ! vrednost 13 19 25 0 0 0
V prvi vrstici so zapisani naslovi v pomnilniku v desetiški obliki. Ti se pričnejo z 10, potrebujemo jih 19.
V drugi vrstici so zapisani znaki zapisanih vrednosti, če jih interpretiramo kot tip char. Ti so zapisani samo na mestih, kjer jih želimo interpretirati kot znake.
V tretji vrstici so zapisane številske vrednosti zapisanih bajtov v desetiški obliki.
Kjer so v 2 vrstici zapisani znaki, v 3 vrstici niso zapisane njihove številske vrednost. Znaki imajo številske vrednosti, ampak ker nas v tem primeru ne zanimajo, so iz jasnosti izpuščene.
Za ta primer bi funkcija vrnila naslov 10 kot tip char**.