Esercizi di lettura di codice C

From Sistemi Operativi
Jump to navigation Jump to search

Questi programmi non sono necessariamente stilisticamente belli (non sono "esempi da copiare") ma presentano dei passaggi di non semplice lettura.

Il consiglio e' di compilarli, provarli e capirne passo-passo il funzionamento. (o spiegare perche' non funzionino).

Ovviamente potete proporre ulteriori esempi. Renzo (talk) 15:42, 10 October 2015 (CEST)

programma 0

#include <stdio.h>

int main(int argc, char *argv[]) {
        int c;
        while ((c=getchar())!=EOF)
                putchar(c);
        return 0;
}

In questo esempio tutto ciò che viene preso in input viene messo in output, fino a quando non viene digitato il carattere di End of File (EOF). Solitamente nei sistemi DOS il carattere EOF viene definito come la combinazione di tasti CTRL+Z, mentre nei sistemi basati su Unix si utilizzano i tasti CTRL+D. (S.G)

Nel caso l'input provenga da file e non da tastiera (ad esempio eseguendo da terminale la riga di comando "./programma0 < testo"), il carattere speciale EOF viene inserito automaticamente alla fine del file "testo". In questo caso il programma prende in input il contenuto di "testo" e stampa a video (carattere per carattere), il contenuto del file, finchè non riconosce il carattere speciale EOF, che fa terminare il ciclo while.



questo è l'esercizio visto in aula. Il punto da notare è l'uso dell'assegnazione all'interno del controllo del ciclo while. E' una struttura tipica del linguaggio C che semplifica sintatticamente la scrittura del programma. Renzo (talk) 10:02, 17 October 2015 (CEST)

programma 1

#include <stdio.h>

char s1[]="hello world";
char *s2="hello world";

void foo(char *s) {
        printf("%s\n",s);
        s[4]=',';
        printf("%s\n",s);
}

int main(int argc, char *argv[]) {
        foo(s1);
        foo(s2);
        return 0;
}

In questo esempio viene mostrata la differenza tra stringa variabile (s1) e stringa costante (s2). Nel primo caso a tempo di compilazione la stringa costante "hello world" viene copiata passo nella stringa variabile s1 consentendo le normali operazioni di modifica della stringa stessa. Mentre nel secondo caso a tempo di compilazione viene creata la stringa costante "hello world" e memorizzato il suo indirizzo nella variabile s2 NON consentendo le normali operazioni di modifica delle stringa stessa in quanto appunto un costante. (S.G)

programma 2

#include <stdio.h>

char t[]="test";
struct st {
        char s[5];
        char *t;
} st0={"test",t};

void foo (struct st i) {
        i.s[2]='n';
        i.t[2]='n';
}

void bar (struct st *i) {
        i->s[2]='x';
        i->t[2]='x';
}

int main(int argc, char *argv[]) {
        printf("%s %s\n",st0.s,st0.t);
        foo(st0); /* Vengono copiati i valori della struttura st0 in i, le uniche modifiche saranno sul campo i.t in quanto puntatore. */
        printf("%s %s\n",st0.s,st0.t); 
        bar(&st0); /* Viene copiato l'indirizzo della struttura st0 in i, le modifiche vengono apportate su tutti i campi delle struttura. */
        printf("%s %s\n",st0.s,st0.t);
        return 0;
}

Output del programma:

test test

test tent

text text

(S.G)


Sì, ma perché? Come è fatta la struttura st? Renzo (talk) 10:06, 17 October 2015 (CEST)


La struttura st è fatta da un array di 5 caratteri e da un puntatore a caratteri (che viene usato anch'esso per puntare ad un'array di caratteri).

Con la prima chiamata a printf vengono stampati i valori dei campi della struttura non ancora modificati, dal momento che non è stata ancora chiamata n&egrave la funzione foo n&egrave la funzione bar (test test)

In seguito viene passata la struct st0 alla funzione foo. In particolare viene passata alla funzione una COPIA del primo campo e una COPIA del secondo campo; tuttavia essendo il secondo campo della struttura un puntatore, viene passato il valore del puntatore, che è un indirizzo, cioè l'indirizzo a cui si trova l'array t (char t[]="test"). Per questo motivo viene modificato solo il valore del secondo campo nella struttura st0, infatti quando la funzione termina, la copia del primo campo viene persa, senza aver modificato il valore originale, mentre il valore del secondo campo viene modificato perchè l'abbiamo modificato tramite l'indirizzo. L'output finale sarà quindi: test tent.

Alla chiamata alla funzione bar, viene invece passato l'INDIRIZZO della struttura st0, e per questo quando andiamo a modificare i campi della struttura all'interno della funzione bar, non stiamo modificando una copia di st0, ma stiamo modificando direttamente st0, accedendo tramite puntatore ai suoi singoli campi. Quindi quando bar termina, i valori originali sono stati modificati e l'output risulterà: text text.

(A.I.)

programma 3

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
        while (argc > 1) {
                int i,j;
                char *s=argv[1];
                for (i=0, j=strlen(s)-1; i < j; i++, j--)
                        s[i] ^= s[j], s[j] ^= s[i], s[i] ^= s[j];
                printf("%s ",s);
                argc--;
                argv++;
        }
        printf("\n");
        return 0;
}

In questo esempio il programma effettua un reverse di tutte le parole che gli vengo date in input ad esclusione di argv[0] che sarebbe il nome del programma, esempio:

INPUT: ./programma3 ciao sistemi operativi

OUTPUT: oaic imetsis ivitarepo

Il punto importante di questo codice è che lo scambio delle singole lettere viene effettuato con l'operatore logico XOR senza cosi avere bisogno di una "variabile di scambio". Qui viene spiegato nel dettaglio il funzionamento dell'operatore logico

(S.G)

programma 4

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct elem {
        char *s;
        struct elem *next;
};
struct elem *root=NULL;

void insert(char *s, struct elem **elp) { /* Inserimento in ordine alfabetico */
        if (*elp && strcmp((*elp)->s, s) < 0) /* se la lista elp non è vuota e se (*elp)->s è alfabeticamente minore di s, inserisce in coda s */
                insert(s,&((*elp)->next));
        else { /* nel caso in cui la lista è vuota o se (*elp)->s è alfabeticamente maggiore di s, inserisce in testa s */
                struct elem tmp={s,*elp};
                struct elem *new=malloc(sizeof(struct elem));
                *new=tmp;
                *elp=new;
        }
}

struct elem *print(struct elem *root) { /* stampa l'intera lista, alla fine dealloca root e restituisce NULL */
        if (root) {
                printf("%s ",root->s);
                root->next=print(root->next);
                free(root);
                return NULL;
        }
}

int main(int argc, char *argv[]) {
        for (;argc>1;argc--,argv++) /* Scorre tutti gli elementi in input del programma (tranne il nome stesso del programma), aggiungendoli in una lista */
                insert(argv[1],&root);
        root=print(root);
        printf("\n");
        return 0;
}

Il programma ordina alfabeticamente gli elementi presi input, esempio:

INPUT: ./programma4 ciao sistemi operativi e macchine virtuali

OUTPUT: ciao e macchine operativi sistemi virtuali

(S.G)


Però ci sono due tecniche diverse per la scrittura di funzioni ricorsive che modificano liste... Chi le spiega? Renzo (talk) 10:08, 17 October 2015 (CEST)

Programma 5

Propongo un esempio relativo all'uso dello statement static:

#include <stdio.h>

int var;

int foo(){
	static int var=0;
	var++;
	return var;
}

int bar(){
	int var=0;
	var ++;
	return var;
}

int foobar(){
	var++;
	return var;
}

int main(int argc, char *argv[]){
	int sum1, sum2, sum3;
	int i;
	for(i=0;i<100;i++){
		sum1=foo();
	}
	printf("%d\n", sum1);
	for(i=0;i<100;i++){
		sum2=bar();
	}
	printf("%d\n", sum2);
	for(i=0;i<100;i++){
		sum3=foobar();
	}
	printf("%d\n", sum3);

	return 0;
}