Discuss the good, the bad and the ugly aspects of their code.
Please be gentle in any criticism - we are all learning!
Do you have any questions? Have you learned anything that would be useful to share with the rest of the tutorial?
What are its inputs and output and what do they mean?
Describe a function that will allocate memory for a struct and assign a pointer to the result.
Malloc() will always return a pointer that will give us the address of this memory. This means we will have a pointer to a variable that won't be cleaned up automatically and we can pass that around between functions etc.
The input to malloc() will be the number of bytes needed to store the variable. We will nearly always use sizeof() to find out this value.
The code below can be useful, but there's not much there. It's more useful to think about what "allocating memory" means. It's basically the idea that we're creating a new variable, except it's only accessible by a pointer and it lasts after the function that created it has returned.
// a generic linked list node (we could use any struct we want here) struct node { int data; struct node * next; }; struct node *makeNode(int inputData) { struct node *n; n = malloc(sizeof (struct node)); return n; }
What is the input to free and how does it help it do what it needs to do?
Discuss why these are extremely dangerous, one of the worst causes of bugs in C programs and a major source of security vulnerabilities.
free(p); printf("%d\n", p->data);Students often incorrectly believe that it is must be safe to access p->data because nothing can have changed.
Commonly free will change the contents of the memory it is given (back) to record its internal housekeeping information.
More generally in a threaded program a malloc could be called in another thread between the free and the printf.
In more complex programs its common mistake for programmers to free some memory, for example holding a struct, but forget that it is still being used elsewhere in their code (probably via different pointer).
As their code keeps executing if malloc is called again to store another struct it is likely to be allocated the recently freed memory.
This means what are meant to be two structs containing different values are now occupying the one piece of memory.
This has disastrous results as assignments to one struct change the other.
Not only is this is very difficult to debug, but malicious users exploit these error (in extremely convoluted ways) to bypass security.
So essentially:
What does dcc --leak-check do?
A memory leak is when a program doesn't free memory allocated with malloc.
This is (generally) not important in the programs we write in COMP1511 because they run only for short periods of time and allocate small amounts of memory.
But if, for example, a web browser allocates memory (calls malloc) every time a user visits a page but doesn't free the memory (call free) when they leave the page, the web browser's memory use will steadily grow, eventually causing performance problems and then if it exhausts available memory, termination.
So we want you to practice free-ing memory in lab exercises.
dcc --leak-check warns you when you haven't freed your memory. It uses an underlying tool named valgrind. It translates valgrind output into something hopefully a COMP1511 student can understand.
Note, the operating system reclaims all memory when a program exits.
struct node *list_append(struct node *list1, struct node *list2);As usual, struct node has this definition:
struct node { int data; struct node *next; };It should not create (malloc) any new list elements.
It should just change the appropriate next field in the first list.
struct node *list_append(struct node *list1, struct node *list2) { struct node *n; if (list1 == NULL) { return list2; } n = list1; while (n->next != NULL) { n = n->next; } n->next = list2; return list1; }
struct node { int data; struct node *next; };
See list_empty.c and list.h for this weeks linked list tute questions, and test_list.c for autotests. Alternatively download all three files as tut_list_files.zip.
struct node *copy(struct node *head);It should call malloc to create a new linked list of the same length and which contains the same data.
struct node *copy(struct node *head) {
if (head == NULL) {
return NULL;
}
struct node *new_head = malloc(sizeof (struct node));
assert(new_head);
new_head->data = head->data;
struct node *last_new_node = new_head;
struct node *p = head->next;
while (p != NULL) {
last_new_node->next = malloc(sizeof (struct node));
last_new_node = last_new_node->next;
assert(last_new_node != NULL);
last_new_node->data = p->data;
p = p->next;
}
last_new_node->next = NULL;
return new_head;
}
A recursive solution:
struct node *copy(struct node *head) {
if (head == NULL) {
return NULL;
}
struct node *new_head = malloc(sizeof (struct node));
assert(new_head);
new_head->data = head->data;
new_head->next = copy(head->next);
return new_head;
}
int identical(struct node *head1, struct node *head2);
int identical(struct node *head1, struct node *head2) {
struct node *p1 = head1;
struct node *p2 = head2;
while (p1 != NULL && p2 != NULL) {
if (p1->data != p2->data) {
return 0;
}
p1 = p1->next;
p2 = p2->next;
}
return p1 == p2;
}
A recursive solution:
int identical(struct node *head1, struct node *head2) {
if (head1 == NULL && head2 == NULL) {
return 1;
}
if (head1 == NULL || head2 == NULL || head1->data != head2->data) {
return 0;
}
return identical(head1->next, head2->next);
}
int ordered(struct node *head);
int ordered(struct node *head) {
if (head == NULL || head->next == NULL) {
return 1;
}
struct node *p = head;
while (p->next->next != NULL) {
if (p->data >= p->next->data) {
return 0;
}
}
return 1;
}
A recursive solution:
int ordered(struct node *head) {
if (head == NULL || head->next == NULL) {
return 1;
}
if (head->data >= head->next->data) {
return 0;
}
return ordered(head->next);
}
The new linked list should also be in strictly increasing order. It should include only elements found in both lists.
set_intersection should call malloc to create the nodes of the new linked list.
set_intersection should have this prototype:
struct node *set_intersection(struct node *set1, struct node *set2);
// set1 and set2 are linked lists in strictly increasing order
// return a new ordered list containing a copy of the contents of both
struct node *set_intersection(struct node *set1, struct node *set2) {
if (set1 == NULL || set2 == NULL) {
return NULL;
}
if (set1->data == set2->data) {
struct node *new_head = malloc(sizeof (struct node));
assert(new_head != NULL);
new_head->data = set1->data;
new_head->next = set_intersection(set1->next, set2->next);
return new_head;
} else if (set1->data < set2->data) {
return set_intersection(set1->next, set2);
} else {
return set_intersection(set1, set2->next);
}
}
The new linked list should also be in strictly increasing order. Elements found in both lists should only occur once in the new linked list.
set_union should call malloc to create the nodes of the new linked list.
set_union should have this prototype:
struct node *set_union(struct node *set1, struct node *set2);
// set1 and set2 are linked lists in strictly increasing order
// return a new ordered list containing a copy of the contents of both
struct node *set_union(struct node *set1, struct node *set2) {
if (set1 == NULL && set2 == NULL) {
return NULL;
}
struct node *new_head = malloc(sizeof (struct node));
assert(new_head != NULL);
if (set1 != NULL && set2 != NULL && set1->data == set2->data) {
new_head->data = set1->data;
new_head->next = set_union(set1->next, set2->next);
} else if (set2 == NULL || (set1 != NULL && set1->data < set2->data)) {
new_head->data = set1->data;
new_head->next = set_union(set1->next, set2);
} else {
new_head->data = set2->data;
new_head->next = set_union(set1, set2->next);
}
return new_head;
}
Your tutor may still choose to cover some of the questions time permitting.
struct node *merge_sorted(struct node *list1, struct node *list2);
It should not create (malloc) any list elements. It should change the appropriate next fields to combined the lists.
Implement this function both iteratively (using a while/for loop) and recursively.
struct node *merge_sorted(struct node *list1, struct node *list2) { struct node *start; struct node *n; if (list1 == NULL) { return list2; } else if (list2 == NULL) { return list1; } else if (list1->data < list2->data) { start = list1; n = list1; list1 = list1->next; } else { start = list2; n = list2; list2 = list2->next; } while (list1 != NULL && list2 != NULL) { if (list1->data < list2->data) { n->next = list1; n = list1; list1 = list1->next; } else { n->next = list2; n = list2; list2 = list2->next; } } if (list1 == NULL) { n->next = list2; } else { n->next = list1; } return start; }Recursive version:
struct node *merge_sorted(struct node *list1, struct node *list2) { if (list1 == NULL) { return list2; } else if (list2 == NULL) { return list1; } else if (list1->data < list2->data) { list1->next = merge_sorted(list1->next, list2); return list1; } else { list2->next = merge_sorted(list1, list2->next); return list2; } }
struct node *strings_to_list(int len, char *strings[]);Assume the strings contain only digit characters,
It might be called like this:
char *powers[] = {"2", "4", "8", 16"}; struct node *head = strings_to_list(4, powers);
// create linked list from array of strings struct node *strings_to_list(int len, char *strings[]) { struct node *head = NULL; for (int i = len - 1; i >= 0; i = i - 1) { struct node *n = malloc(sizeof (struct node)); assert(n != NULL); n->next = head; n->data = atoi(strings[i]); head = n; } return head; }
int main(int argc, char *argv[]) { struct node *head = strings_to_list(argc - 1, &argv[1]); ...
Assume, a command line argument of "-" separates the arguments to be converted.
int main(int argc, char *argv[]) { // create two linked lists from command line arguments int dash_arg = argc - 1; while (dash_arg > 0 && strcmp(argv[dash_arg], "-") != 0) { dash_arg = dash_arg - 1; } struct node *head1 = strings_to_list(dash_arg - 1, &argv[1]); struct node *head2 = strings_to_list(argc - dash_arg - 1, &argv[dash_arg + 1]);
struct node *insert_ordered(int item, struct node *list);It should create (malloc) just 1 list element and change the appropriate next field in the list to insert it.
Implement this function.
struct node *insert_ordered(int item, struct node *list) { struct node *n; struct node *new = malloc(sizeof *new); if (new == NULL) { fprintf(stderr, "Out of memory"); exit(1); } new->data = item; if (list == NULL || item < list->data) { new->next = list; return new; } n = list; while (n->next != NULL && n->next->data < item) { n = n->next; } new->next = n->next; n->next = new; return list; }
double ff[] = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6}; double *fp = &ff[0];
What are the similarities between
ff
and fp
? What are the
differences?
ff
. They can both be used to access the
array.
char s[] = "Hello World!"; char *cp = s; char *cp2 = &s[8];
What is the output when the following statements are executed?
printf("%s\n", cp); printf("%c\n", *cp); printf("%c\n", cp[6]); printf("%s\n",cp2); printf("%c\n",*cp2);
Hello World! H W rld! r
int non_decreasing(int n, int a[n])which checks whether items in an array are sorted in non-decreasing order. (i.e. a[i] ≥ a[i-1], for 0<i<N). Your function should returns 1 if the items are in non-decreasing order, 0 otherwise.
int non_decreasing(int a[], int n) { int no_decrease = 1; for (int i = 0; i < n-1 && no_decrease; i = i + 1) { if (a[i] > a[i + 1]) { no_decrease = 0; } } return no_decrease; }
int find_index(int x, int n, int a[n])which takes two integers x and n together with an array a[] of n integers and searches for the specified value within the array. Your function should return the smallest index k such that a[k] is equal to x (or -1 if x does not occur in the array).
int find_index(int x, int n, int a[n]) { int k = -1; for (int i = 0; i < n && k == -1; i = i + 1) { if (a[i] == x) { k = i; } } return k; }
c
in the
string s. It returns NULL if the
character cannot be found.
char *strrchr(char s[], char c)
char *strrchr(char s[], char c) { char *ptr = NULL; int i; for (i = 0; i < strlen(s); i = i + 1) { if (s[i] == c) { ptr = &s[i]; } } return ptr; }
Use this function from the math.h library:
double fabs(double x);
#include <math.h> double manhattan_distance(double x1, double y1, double x2, double y2) { return fabs(x1 - x2) + fabs(y1 - y2); }
multiply.c
that performs
addition or multiplication of integers, as follows.
Your program should continually read integers from the
user until end-of-input is encountered. Then it should
print either the sum or the product of the input
numbers. The program behaviour is controlled by either
of the following command-line arguments:
-add, -multiply
. If the wrong
command-line argument(s) are supplied your program
should do nothing.
multiply.c
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
int val, result;
if (argc != 2) {
return 0; // should print a message here
}
if (strcmp(argv[1], "-add") != 0 && strcmp(argv[1], "-multiply") != 0) {
return 0; // should print a message here
}
result = 0;
//If we are multiplying we need to initialise result to 1
//Since we have already checked argv[1] contains either
//"-multiply" or "-add" we only need to check the second char
if (argv[1][1] == 'm') {
result = 1;
}
while (scanf("%d", &val) == 1) {
if (argv[1][1] == 'a') {
result = result + val;
} else {
result = result * val;
}
}
if (argv[1][1] == 'a') {
printf("Sum: %d\n", result);
} else {
printf("Product: %d\n", result);
}
return 0;
}
thirteen_stdin.c
which
reads 2 integers from standard input and then prints
all integers divisible by 13 between those numbers.
Your program should behave like this:
./a.out Enter start: 10 Enter finish: 42 13 26 39
thirteen_stdin.c
#include <stdio.h>
int main(void) {
int i, lower, upper;
printf("Enter start: ");
scanf("%d", &lower);
printf("Enter finish: ");
scanf("%d", &upper);
i = lower + 1;
while (i < upper) {
if (i % 13 == 0) {
printf("%d\n", i);
}
i = i + 1;
}
return 0;
}
Your program should behave like this:
./a.out 10 42 13 26 39
thirteen_atoi.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int i, lower, upper;
lower = atoi(argv[1]);
upper = atoi(argv[2]);
i = lower + 1;
while (i < upper) {
if (i % 13 == 0) {
printf("%d\n", i);
}
i = i + 1;
}
return 0;
}
If error checking was required - what checking would you add to the programs from the previous 2 questions?
Check two arguments present (argc == 3).
In both cases you might check first number is not greater than first.
You might check arguments or input are numeric (more difficult)
median.c
which reads
integers until end-of-input is reached. It should then
print the median (middle) of the integers. If there
are an even number of integer you can print either of
the two middle integers.
Assume the numbers of integer is > 0 and < 1000.
Assume the integer are entered in sorted (non-decreasing) order.
Your program should behave like this:
./a.out 1 2 4 8 16 5 numbers read. Median was 4
median.c
#include <stdio.h>
#define MAX 1000
int main(void) {
int x[MAX];
int number;
int numbers_read;
numbers_read = 0;
while (scanf("%d", &number) == 1) {
x[numbers_read] = number;
numbers_read = numbers_read + 1;
}
printf("%d numbers read. Median was %d\n", numbers_read, x[numbers_read/2]);
return 0;
}
median.c
#include <stdio.h>
#define MAX 1000
int main(void) {
int x[MAX];
int number;
int numbers_read;
numbers_read = 0;
while (scanf("%d", &number) == 1 && numbers_read < MAX) {
if (numbers_read > 0 && number < x[numbers_read - 1]) {
printf("Numbers not in order\n");
return 1;
}
x[numbers_read] = number;
numbers_read = numbers_read + 1;
}
if (numbers_read > 1) {
printf("%d numbers read. Median was %d\n", numbers_read, x[numbers_read/2]);
}
return 0;
}
./a.out 16 8 2 1 4 5 numbers read. Median was 4
median.c
#include <stdio.h>
#define MAX 1000
int main(void) {
int x[MAX];
int i, number, numbers_read;
numbers_read = 0;
while (scanf("%d", &number) == 1 && numbers_read < MAX) {
i = numbers_read;
while (i > 0 && x[i - 1] > number) {
x[i] = x[i - 1];
i = i - 1;
}
x[i] = number;
numbers_read = numbers_read + 1;
}
if (numbers_read > 1) {
printf("%d numbers read. Median was %d\n", numbers_read, x[numbers_read/2]);
}
return 0;
}
Since the length of the array is variable you should not create additional arrays, nor assume a maximum array length. You may write extra functions in answering this question. Your function should have the following prototype:
int distinct_nums(int size, int nums[size]);Running the function with the following input:
int nums[] = {7,3,1,4,7,3,6,5,3}; int num_distinct = distinct_nums(9, nums);Should return the value 6 and the first six elements of the array should be changed to: {7,3,1,4,6,5}
#include <stdio.h>
int distinct_nums(int size,int nums[size]);
void print_array(int size, int nums[size]);
int main(int argc, char * argv[]){
int nums[] = {7,3,1,4,7,3,6,5,3};
print_array(9, nums);
int num_distinct = distinct_nums(9, nums);
print_array(num_distinct, nums);
return 0;
}
// Moves the distinct numbers down to the front of the array
int distinct_nums(int size, int nums[size]) {
int distinct = 0;
// search through the distinct part of the array to see if
// it is a duplicate.
for (int i = 0 ; i < size; i = i + 1) {
int seen = 0;
for (int j = 0; j < distinct; j = j + 1) {
if (nums[i] == nums[j]) {
seen = 1;
}
}
// If it is distinct, move down into the next distinct location
if (seen == 0) {
nums[distinct] = nums[i];
distinct = distinct + 1;
}
}
return distinct;
}
void print_array(int size, int nums[size]) {
for (int i = 0; i < size; i = i + 1) {
printf("%d ",nums[i]);
}
printf("\n");
}
void scalar_multiply(int rows, int columns, int matrix[rows][columns], int scalar){ int i,j; for (i = 0;i < rows; i = i + 1) { for (j = 0;j < columns; j = j + 1) { matrix[i][j] = matrix[i][j] * scalar; } } }
int remove_char(char str[], char c)
int remove_char(char str[], char c) { int i; // Find the first occurrence of the character c i = 0 while (str[i] != '\0' && str[i] != c) { i = i + 1; } if (str[i] == '\0') { return 0; } // We found the letter, do shift all the letters // after it, down one cell in the array while (str[i] != '\0') { str[i] = str[i + 1]; i = i + 1; } return 1; }
For example "carpark" and "carpet" have a common prefix length of 4.
int prefix_len(char s1[], char s2[]) { int i = 0; while (s1[i] == s2[i] && s1[i] != '\0') { i = i + 1 } return i; }
// text - the array of strings // array_size - the number of strings in the array // num_chars - print out any strings in the array with more than this number // of characters void print_if_longer(int array_size, char text[array_size][MAX_LEN], int num_chars);
void print_if_longer(char *text[],int array_size, int num_chars){ int i; for (i = 0; i < array_size; i = i + 1) { if (strlen(text[i]) > num_chars) { printf("%s",text[i]); } } }
int x = -9; int *p1 = &x; int *p2; p2 = p1; printf("%d\n", *p2); *p2 = 10; printf("%d\n",x);
-9 10
int x = -9; int y = 0; while (x != 0){ y = y - 1; x = x + 1; } printf("%d\n", x); printf("%d\n",y);
0 -9
int i = -7; int j = 0; while (i != 0){ j = j - i; i = i + 1; } printf("%d\n", i); printf("%d\n",j);
0 -28
char goals[] = "All your goals belong to us."; char *a, *b, *c; a = goals + 5; b = &goals[10]; c = goals + (b - goals) + (b - a);The fragment is valid C. It executes without error. Indicate clearly and exactly what the following expressions evaluate to:
a == goals
a > goals
goals > c
c - b
goals - a
a[0] != b[0]
*c
goals[a - goals] == *a
c[a - b]
int i = 0; int j = 0; char *s = "ceded"; while (s[i] != '\0') { j = j + s[i] - 'a'; i = i + 1; } printf("%d %d\n", i, j);The fragment is valid C. It executes without error. Indicate clearly and exactly what output will be printed.
5 16
Here, s is a char pointer that
points to the first letter of the string "str".
We can index into it since strings are arrays.
The loop terminates when s[i] is
'\0', i.e., 0, which happens when
i is 5.
Indexing into s gives individual
characters; subtracting 'a' from each of
those gives the 'distance' between the character
and 'a', e.g.,
s[0] - 'a' => 'c' - 'a' = 2.
void sum_prod(int len, int nums[len], int *sum, int *product);
#include <stdio.h>
void sum_prod(int len, int nums[len], int *sum, int *product);
int main(int argc, char *argv[]){
int nums[] = {3,4,1,5,6,1};
int prod;
int sum;
//Pass in the address of the sum and product variables
sum_prod(6, nums, &sum, &prod);
printf("The sum is %d and prod is %d\n",sum,prod);
return 0;
}
// Calculates the sum and product of the array nums.
// Actually modifies the variables that *sum and *product are pointing to
void sum_prod(int len, int nums[len], int *sum, int *product) {
int i;
*sum = 0;
*product = 1;
for (i = 0; i < len; i = i + 1) {
*sum = *sum + nums[i];
*product = *product * nums[i];
}
}
int find_char(char str[], char c) { if (str[0] == c) { return 1; } else if (str[0] == '\0') { return 0; } else { return find_char(&str[1], c); } }