Esercitazione 1 - 7 ottobre 2016 (150 min)
Configurazione
Nelle esercitazioni lavoreremo con Linux LXLE nella VM Oracle Virtualbox BIAR.
Per questa prima esercitazione, può essere utile avere a disposizione le pagine
man di alcune funzioni C. Seguire i seguenti passi:
- aprire un terminale (icona sul dock in basso)
- digitare sudo apt-get install manpages-dev, inserire la password amministratore biar e dare conferma
Creare una directory di lavoro
sc1617 e posizionarsi nella directory creata come segue:
$ mkdir /home/biar/Desktop/sc1617
$ cd /home/biar/Desktop/sc1617
Esercizio 1 (duplicazione di stringhe)
L'obiettivo di questo esercizio è individuare e correggere un errore comune nell'utilizzo di funzioni di libreria C. Si immetta il seguente programma di esempio nel file
strdup.c:
#include <stdio.h>
#include <string.h>
int main
(int argc,
char* argv
[]) {
char* prog_name = strdup
(argv
[0]);
printf("binary name: %s\n", prog_name
);
return 0;
}
Compilare il programma includendo le informazioni di debugging, quindi farlo eseguire all'interno
valgrind.
$ gcc -g -o strdup strdup.c
$ valgrind ./strdup
Che tipologia di errore viene riportato? Correggerlo, quindi rieseguire nuovamente il programma usando
valgrind.
Esercizio 2 (concatenazione di stringhe)
Il seguente frammento di codice può portare ad una corruzione dell'immagine di memoria di un processo:
#include <string.h>
#include <stdio.h>
#define BUFLEN 16
int main
() {
char s1
[BUFLEN
], s2
[BUFLEN
];
strcpy
(s1,
"This is source");
sprintf
(s2,
"%s",
"This is destination");
strcat
(s2, s1
);
printf("Final string: |%s|", s2
);
return 0;
}
Per scoprire cosa fanno le funzioni
strcpy,
sprintf e
strcat consultare la sezione 3 del manuale in linea (ad esempio,
man 3 strcat). Compilare ed eseguire il programma come per l'Esercizio 1: che tipo di errore si verifica? Proporre una soluzione al problema.
Esercizio 3 (manipolazione di stringhe)
Il seguente programma manipola una stringa in input mettendo ogni lettera al suo interno in lowercase:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
char* to_lower
(const char *str
) {
char* l = strdup
(str
);
char* c;
for (c = l; *c; c++
) {
if (isupper
(*c
))
*c = tolower
(*c
);
}
return l;
}
int main
(int argc,
char* argv
[])
{
if (argc !=
2) return 1;
char* lower = to_lower
(argv
[1]);
while (*lower
)
printf("%c",
(*
(lower++
)));
printf("\n");
return 0;
}
Quali errori sono presenti al suo interno?
Esercizio 4 (navigazione nell'Atlantico)
Il codice seguente è stato adattato da un programma che simula l'avanzamento dell'RMS Titanic nell'Oceano Atlantico:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BOAT_SPEED 1
char* full_steam_ahead
(unsigned current_distance
) {
unsigned new_distance = current_distance + BOAT_SPEED;
char* log_text_suffix =
"miles, go ahead!\n";
int buffer_length = strlen
("log_text_suffix") +
3;
char* buffer;
sprintf
(buffer,
"%u %s", new_distance, log_text_suffix
);
return buffer;
}
int main
(int argc,
char* argv
[]) {
int to_the_atlantic;
int nautical_miles;
// check number of arguments
if (argc <
2) {
printf("Syntax: %s <miles>\n", argv
[0]);
return 1;
}
// parse argv[1]'s content as an int using atoi()
to_the_atlantic = atoi
(argv
[1]);
if (to_the_atlantic <
1) {
printf("Specify a positive (non-zero) amount!\n");
return 1;
}
// advance the boat towards the Atlantic as requested by the user
char* captains_log;
for (nautical_miles =
1; nautical_miles <= to_the_atlantic; nautical_miles++
) {
captains_log = full_steam_ahead
(nautical_miles
);
printf("%s", captains_log
);
}
free
(captains_log
);
return 0;
}
Al suo interno sono presenti un certo numero di bug identificabili tramite esecuzione sotto
valgrind. Individuarli utilizzando più valori di input (#argv[1]# conterrà il numero di miglia marine da percorrere) e correggerli.
Esercizio 5 (liste collegate)
Di seguito è riportata una implementazione buggata di una lista collegata in C:
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
char * id;
char * value;
struct node * next;
} node_t;
static int count =
0;
node_t* add_node
(node_t* l,
char* value
) {
node_t* node = malloc
(sizeof(node_t
));
char id
[16];
sprintf
(id,
"ID_%d", count++
);
node->id = id;
node->value = value;
if (l !=
NULL)
node->next = l;
return node;
}
void print_list
(node_t* head
) {
node_t* current = head;
while (current !=
NULL) {
printf("%s\n", current->value
);
current = current->next;
}
}
void delete_list
(node_t * head
) {
node_t* current = head;
while (current !=
NULL) {
free
(current
);
free
(current->value
);
current = current->next;
}
}
int main
() {
node_t* l;
l = add_node
(NULL,
"Hello");
l = add_node
(l,
" ");
l = add_node
(l,
"World");
l = add_node
(NULL,
"!");
print_list
(l
);
delete_list
(l
);
}
Individuare gli errori con l'ausilio di
valgrind e proporre una soluzione che utilizzi correttamente la memoria.
Esercizio 6 (debugging in gdb)
Il frammento di codice seguente calcola l'i-esimo numero della
successione di Fibonacci specificato dall'utente. In particolare, esegue una implementazione ricorsiva ed una variante iterativa dell'algoritmo di calcolo:
#include <stdio.h>
#include <stdlib.h>
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Mathematics/Fibonacci_Number_Program#Recursive_version
unsigned int fib
(unsigned int n
) {
if (n <
2) return n;
else return fib
(n -
1) + fib
(n -
2);
}
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Mathematics/Fibonacci_Number_Program#Iterative_version
unsigned int fib_iter
(unsigned int n
) {
unsigned int i =
0, j =
1, k, t;
for (k =
1; k <= n; ++k
) {
t = i + j;
i = j;
j = t;
}
return j;
}
int main
(int argc,
char* argv
[]) {
if (argc <
2) {
printf("Syntax: %s <n>\n", argv
[0]);
return 1;
}
unsigned int n = atoi
(argv
[1]);
printf("[%u] ric: %u iter: %u\n", n, fib
(n
), fib_iter
(n
));
return 0;
}
I due metodi
fib e
fib_iter sono stati presi da Wikibooks ma restituiscono risultati difformi. Risalire alla causa di questa difformità con l'ausilio di
gdb, in particolare utilizzando breakpoint (possibilmente condizionali) per ispezionare il compo