scanf()
malloc()
❖ Pointers |
Reminder: In a linked list …
Benefits:
In C, linked lists are implemented using pointers and dynamic memory allocation
❖ Sidetrack: Numeral Systems |
Numeral system … system for representing numbers using digits or other symbols.
❖ ... Sidetrack: Numeral Systems |
Decimal representation
… | 1000 | 100 | 10 | 1 |
… | 103 | 102 | 101 | 100 |
❖ ... Sidetrack: Numeral Systems |
Binary representation
… | 8 | 4 | 2 | 1 |
… | 23 | 22 | 21 | 20 |
0b1101
❖ ... Sidetrack: Numeral Systems |
Hexadecimal representation
… | 4096 | 256 | 16 | 1 |
… | 163 | 162 | 161 | 160 |
0x3AF1
❖ Exercise : Conversion Between Different Numeral Systems |
74
0x2D
0b1011111000101001
1011111000101001
0x12D
❖ Memory |
Computer memory … large array of consecutive data cells or bytes
If we declare a variable called
|
|
❖ ... Memory |
Example:
int k; int m; printf("address of k is %p\n", &k); printf("address of m is %p\n", &m);
address of k is BFFFFB80 address of m is BFFFFB84
This means that
Note the use of k
BFFFFB80
BFFFFB83
m
BFFFFB84
BFFFFB87
%p
❖ ... Memory |
When an array is declared, the elements of the array are guaranteed to be stored in consecutive memory locations:
int array[5]; for (i = 0; i < 5; i++) { printf("address of array[%d] is %p\n", i, &array[i]); }
address of array[0] is BFFFFB60 address of array[1] is BFFFFB64 address of array[2] is BFFFFB68 address of array[3] is BFFFFB6C address of array[4] is BFFFFB70
❖ Application: Input Using scanf() |
Standard I/O function scanf()
scanf()
printf()
%d
#include <stdio.h> … int answer; printf("Enter your answer: "); scanf("%d", &answer);
%f
%lf
double
float e; printf("Enter e: "); scanf("%f", &e);
scanf()
scanf()
scanf()
❖ Exercise : Using scanf |
Write a program that
#include <stdio.h> void collatz(int n) { printf("%d\n", n); while (n != 1) { if (n % 2 == 0) n = n / 2; else n = 3*n + 1; printf("%d\n", n); } } int main(void) { int n; printf("Enter a positive number: "); if (scanf("%d", &n) == 1 && (n > 0)) /* test if scanf successful and returns positive number */ collatz(n); return 0; }
❖ Pointers |
A pointer …
The number of memory cells needed for a pointer depends on the computer's architecture:
0x0000
0xFFFF
0x00000000
0xFFFFFFFF
❖ ... Pointers |
Suppose we have a pointer p
char
c
Assuming that the pointer p
c
❖ ... Pointers |
Now that we have assigned to p the address of variable c
*
c
p
*p = 'T'; // sets the value of c to 'T'
*
❖ ... Pointers |
Things to note:
// a potential pointer to any object of type char char *s; // a potential pointer to any object of type int int *p;
p
x
*p
x
❖ Examples of Pointers |
int *p; int *q; // this is how pointers are declared int a[5]; int x = 10, y; p = &x; // p now points to x *p = 20; // whatever p points to is now equal to 20 y = *p; // y is now equal to whatever p points to p = &a[2]; // p points to an element of array a[] q = p; // q and p now point to the same thing
❖ Exercise : Pointers |
What is the output of the following program?
1 #include <stdio.h> 2 3 int main(void) { 4 int *ptr1, *ptr2; 5 int i = 10, j = 20; 6 7 ptr1 = &i; 8 ptr2 = &j; 9 10 *ptr1 = *ptr1 + *ptr2; 11 ptr2 = ptr1; 12 *ptr2 = 2 * (*ptr2); 13 printf("Val = %d\n", *ptr1 + *ptr2); 14 return 0; 15 }
❖ ... Examples of Pointers |
Can we write a function to "swap" two variables?
The wrong way:
void swap(int a, int b) { int temp = a; // only local "copies" of a and b will swap a = b; b = temp; } int main(void) { int a = 5, b = 7; swap(a, b); printf("a = %d, b = %d\n", a, b); // a and b still have their original values return 0; }
❖ ... Examples of Pointers |
In C, parameters are "call-by-value"
swap()
a
b
main()
❖ ... Examples of Pointers |
Can we write a function to "swap" two variables?
The right way:
void swap(int *p, int *q) { int temp = *p; // change the actual values of a and b *p = *q; *q = temp; } int main(void) { int a = 5, b = 7; swap(&a, &b); printf("a = %d, b = %d\n", a, b); // a and b now successfully swapped return 0; }
❖ Pointer Arithmetic |
A pointer variable holds a value which is an address.
C knows what type of object is being pointed to
sizeof
int a[6]; // assume array starts at address 0x1000 int *p; p = &a[0]; // p contains 0x1000 p = p + 1; // p now contains 0x1004
❖ ... Pointer Arithmetic |
For a pointer declared as T *p;
T
A
p = p + k;
k
p
A + k*sizeof(T)
k
Example:
int a[6]; (addr 0x1000) char s[10]; (addr 0x2000) int *p; (p == ?) char *q; (q == ?) p = &a[0]; (p == 0x1000) q = &s[0]; (q == 0x2000) p = p + 2; (p == 0x1008) q++; (q == 0x2001)
❖ Pointers and Arrays |
An alternative approach to iteration through an array:
int a[6]; int *p; p = &a[0]; while (p <= &a[5]) { printf("%2d ", *p); p++; }
❖ ... Pointers and Arrays |
Pointer-based scan written in more typical style
Note: because of pointer/array connection a[i] == *(a+i)
❖ Arrays of Strings |
One common type of pointer/array combination are the command line arguments
seqq
./seqq 10 20
then seqq
"10", "20"
❖ ... Arrays of Strings |
./seqq 10 20
Each element of argv[]
char *
\0
❖ ... Arrays of Strings |
main()
int main(int argc, char *argv[]) { ...
argc
argc == 1
argv[]
argv[0]
argv[1],argv[2],…
<stdlib.h>
atoi(char *s)
atof(char *s)
float
❖ Exercise : Command Line Arguments |
Write a program that
#include <stdio.h> #include <stdlib.h> void collatz(int n) { ... } int main(int argc, char *argv[]) { if (argc != 2) { printf("Usage: %s number\n", argv[0]); return 1; } int n = atoi(argv[1]); if (n > 0) collatz(n); return 0; }
❖ ... Arrays of Strings |
argv
⇒ Alternative prototype for main()
int main(int argc, char **argv) { ...
Can still use argv[0]
argv[1]
❖ Pointers and Structures |
Like any object, we can get the address of a struct
&
typedef char Date[11]; // e.g. "03-08-2017" typedef struct { char name[60]; Date birthday; int status; // e.g. 1 (≡ full time) float salary; } WorkerT; WorkerT w; WorkerT *wp; wp = &w; // a problem ... *wp.salary = 125000.00; // does not have the same effect as w.salary = 125000.00; // because it is interpreted as *(wp.salary) = 125000.00; // to achieve the correct effect, we need (*wp).salary = 125000.00; // a simpler alternative is normally used in C wp->salary = 125000.00;
Learn this well; we will frequently use it in this course.
❖ ... Pointers and Structures |
General principle …
If we have:
SomeStructType s;
SomeStructType *sp = &s; // declare pointer and initialise to address of s
then the following are all equivalent:
s.SomeElem sp->SomeElem (*sp).SomeElem
❖ Memory |
Reminder:
Computer memory … large array of consecutive data cells or bytes
|
|
❖ C execution: Memory |
An executing C program partitions memory into:
malloc()
❖ Exercise : Memory Regions |
int numbers[] = { 40, 20, 30 }; void insertionSort(int array[], int n) { int i, j; for (i = 1; i < n; i++) { int element = array[i]; for (j = i-1; j >= 0 && array[j] > element; j--) array[j+1] = array[j]; array[j+1] = element; } } int main(void) { insertionSort(numbers, 3); return 0; }
Which memory region are the following objects located in?
insertionSort()
numbers[0]
n
array[0]
element
❖ Dynamic Memory Allocation |
So far, we have considered static memory allocation
int x; // 4 bytes containing a 32-bit integer value char *cp; // 8 bytes (on CSE machines) // containing address of a char typedef struct {float x; float y;} Point; Point p; // 8 bytes containing two 32-bit float values char s[20]; // array containing space for 20 1-byte chars
❖ ... Dynamic Memory Allocation |
In many applications, fixed-size data is ok.
In many other applications, we need flexibility.
Examples:
char name[MAXNAME]; // how long is a name? char item[MAXITEMS]; // how high can the stack grow? char dictionary[MAXWORDS][MAXWORDLENGTH]; // how many words are there? // how long is each word?
With fixed-size data, we need to guess sizes ("large enough").
❖ ... Dynamic Memory Allocation |
Fixed-size memory allocation:
❖ Dynamic Data Example |
Problem:
6 25 -1 999 42 -16 64
How to define the vector?
❖ ... Dynamic Data Example |
Suggestion #1: allocate a large vector; use only part of it
#define MAXELEMS 1000 // how many elements in the vector int numberOfElems; scanf("%d", &numberOfElems); assert(numberOfElems <= MAXELEMS); // declare vector and fill with user input int i, vector[MAXELEMS]; for (i = 0; i < numberOfElems; i++) scanf("%d", &vector[i]);
Works ok, unless too many numbers; usually wastes space.
Recall that assert()
❖ ... Dynamic Data Example |
Suggestion #2: create vector after count read in
#include <stdlib.h> // how many elements in the vector int numberOfElems; scanf("%d", &numberOfElems); // declare vector and fill with user input int i, *vector; size_t numberOfBytes; numberOfBytes = numberOfElems * sizeof(int); vector = malloc(numberOfBytes); assert(vector != NULL); for (i = 0; i < numberOfElems; i++) scanf("%d", &vector[i]);
Works unless the heap is already full (very unlikely)
Reminder: because of pointer/array connection &vector[i] == vector+i
❖ ... The malloc() |
malloc()
void *malloc(size_t n);
What the function does:
n
NULL
size_t
unsigned int
❖ ... The malloc() |
Things to note about void *malloc(size_t)
stdlib.h
(void *)
NULL
* sizeof(
)
❖ Exercise : Dynamic Memory Allocation |
Write code to
typedef struct { int day, month, year; } DateT; typedef struct { int hour, minute; } TimeT; typedef struct { char plate[7]; DateT d; TimeT t; } TicketT;
TicketT *tickets; tickets = malloc(1000 * sizeof(TicketT)); assert(tickets != NULL);
28,000 bytes allocated
float **matrix; // allocate memory for m pointers to beginning of rows matrix = malloc(m * sizeof(float *)); assert(matrix != NULL); // allocate memory for the elements in each row int i; for (i = 0; i < m; i++) { matrix[i] = malloc(n * sizeof(float)); assert(matrix[i] != NULL); }
8m + 4·mn bytes allocated
tickets
*tickets
malloc
❖ ... The malloc() |
malloc()
Things to note about objects allocated by malloc()
free()
malloc()
❖ ... The malloc() |
Usage of malloc()
int *vector, length, i; … vector = malloc(length*sizeof(int)); // but malloc() might fail to allocate assert(vector != NULL); // now we know it's safe to use vector[] for (i = 0; i < length; i++) { … vector[i] … }
Alternatively:
int *vector, length, i; … vector = malloc(length*sizeof(int)); // but malloc() might fail to allocate if (vector == NULL) { fprintf(stderr, "Out of memory\n"); exit(1); } // now we know its safe to use vector[] for (i = 0; i < length; i++) { … vector[i] … }
fprintf(stderr, …)
stderr
exit(v)
v
❖ Memory Management |
void free(void *ptr)
malloc()
*ptr
*ptr
malloc()
free()
❖ ... Memory Management |
Warning! Warning! Warning! Warning!
Careless use of malloc()
free()
malloc()
free()
Must be very careful with your use of malloc()
free()
❖ ... Memory Management |
If an uninitialised or otherwise invalid pointer is used, or an array is accessed with a negative or out-of-bounds index, one of a number of things might happen:
segmentation fault
❖ ... Memory Management |
Given a pointer variable:
NULL
❖ ... Memory Management |
Typical usage pattern for dynamically allocated objects:
// single dynamic object e.g. struct Type *ptr = malloc(sizeof(Type)); // declare and initialise assert(ptr != NULL); … use object referenced by ptr e.g. ptr->name … free(ptr); // dynamic array with "nelems" elements int nelems = NumberOfElements; ElemType *arr = malloc(nelems*sizeof(ElemType)); assert(arr != NULL); … use array referenced by arr e.g. arr[4] … free(arr);
❖ Memory Leaks |
Well-behaved programs do the following:
malloc()
free()
free()
Such programs may eventually exhaust available heapspace.
❖ Exercise : Dynamic Arrays |
Write a C-program that
❖ Sidetrack: Standard I/O Streams, Redirects |
Standard file streams:
stdin
stdout
stderr
fprintf(stdout, …)
printf(…)
fprintf(stderr, …)
main(…)
stdin
stdout
stderr
❖ ... Sidetrack: Standard I/O Streams, Redirects |
The streams stdin
stdout
stderr
stdin
myprog < input.data
stdout
myprog > output.data
stderr
myprog 2> error.data
❖ Self-referential Structures |
Reminder: To realise a "chain of elements", need a node containing
typedef struct node { int data; struct node *next; } NodeT;
❖ ... Self-referential Structures |
Note that the following definition does not work:
typedef struct { int data; NodeT *next; } NodeT;
Because NodeT
next
The following is also illegal in C:
struct node { int data; struct node recursive; };
Because the size of the structure would have to satisfy
sizeof(struct node) = sizeof(int) + sizeof(struct node) = ∞
❖ Memory Storage for Linked Lists |
Linked list nodes are typically located in the heap
❖ ... Memory Storage for Linked Lists |
Create a new list node:
NodeT *makeNode(int v) { NodeT *new = malloc(sizeof(NodeT)); assert(new != NULL); new->data = v; // initialise data new->next = NULL; // initialise link to next node return new; // return pointer to new node }
❖ Iteration over Linked Lists |
When manipulating list elements
p
NodeT *p
p->data
p->next
p
p
p
p
NULL
❖ ... Iteration over Linked Lists |
Standard method for scanning all elements in a linked list:
NodeT *list; // pointer to first Node in list NodeT *p; // pointer to "current" Node in list p = list; while (p != NULL) { … do something with p->data … p = p->next; } // which is frequently written as for (p = list; p != NULL; p = p->next) { … do something with p->data … }
❖ ... Iteration over Linked Lists |
Check if list contains an element:
int inLL(NodeT *list, int d) { NodeT *p; for (p = list; p != NULL; p = p->next) if (p->data == d) // element found return true; return false; // element not in list }
Print all elements:
void showLL(NodeT *list) { NodeT *p; for (p = list; p != NULL; p = p->next) printf("%6d", p->data); }
❖ Modifying a Linked List |
Insert a new element at the beginning:
NodeT *insertLL(NodeT *list, int d) { NodeT *new = makeNode(d); // create new list element new->next = list; // link to beginning of list return new; // new element is new head }
Delete the first element:
NodeT *deleteHead(NodeT *list) { assert(list != NULL); // ensure list is not empty NodeT *head = list; // remember address of first element list = list->next; // move to second element free(head); return list; // return pointer to second element }
What would happen if we didn't free
head
void freeLL(NodeT *list) { NodeT *p, *temp; p = list; while (p != NULL) { temp = p->next; free(p); p = temp; } }
Why do we need the extra variable temp
❖ Doubly-linked Lists |
Doubly-linked lists are a variation on "standard" linked lists where each node has a pointer to the previous node as well as a pointer to the next node.
You can find one possible implementation of the above doubly-linked list ADT in the directory "dllist", under "Programs" for this lecture.
.
❖ Abstract Data Types |
Reminder: An abstract data type is …
❖ ... Abstract Data Types |
Typical operations with ADTs
❖ ... Abstract Data Types |
ADT interface provides
stack *
❖ Stack as ADT |
Interface (in stack.h
// provides an opaque view of ADT typedef struct StackRep *stack; // set up empty stack stack newStack(); // remove unwanted stack void dropStack(stack); // check whether stack is empty int StackIsEmpty(stack); // insert an int on top of stack void StackPush(stack, int); // remove int from top of stack int StackPop(stack);
ADT stack
StackRep
❖ Sidetrack: Defining Structures |
Structures can be defined in two different styles:
typedef struct { int day, month, year; } DateT; // which would be used as DateT somedate; // or struct date { int day, month, year; }; // which would be used as struct date anotherdate;
The definitions produce objects with identical structures.
It is possible to combine both styles:
typedef struct date { int day, month, year; } DateT;
// which could be used as
DateT date1, *dateptr1;
struct date date2, *dateptr2;
❖ Stack ADT Implementation |
Linked list implementation (stack.c
Remember:
stack.h
typedef struct StackRep *stack;
#include <stdlib.h>
#include <assert.h>
#include "stack.h"
typedef struct node {
int data;
struct node *next;
} NodeT;
typedef struct StackRep {
int height; // #elements on stack
NodeT *top; // ptr to first element
} StackRep;
// set up empty stack
stack newStack() {
stack S = malloc(sizeof(StackRep));
S->height = 0;
S->top = NULL;
return S;
}
// remove unwanted stack
void dropStack(stack S) {
NodeT *curr = S->top;
while (curr != NULL) { // free the list
NodeT *temp = curr->next;
free(curr);
curr = temp;
}
free(S); // free the stack rep
}
// check whether stack is empty
int StackIsEmpty(stack S) {
return (S->height == 0);
}
// insert an int on top of stack
void StackPush(stack S, int v) {
NodeT *new = malloc(sizeof(NodeT));
assert(new != NULL);
new->data = v;
// insert new element at top
new->next = S->top;
S->top = new;
S->height++;
}
// remove int from top of stack
int StackPop(stack S) {
assert(S->height > 0);
NodeT *head = S->top;
// second list element becomes new top
S->top = S->top->next;
S->height--;
// read data off first element, then free
int d = head->data;
free(head);
return d;
}
❖ Sidetrack: Make/Makefiles |
Compilation process is complex for large systems.
How much to compile?
make
❖ ... Sidetrack: Make/Makefiles |
make
Makefile
A dependency specifies
target : source1 source2 … commands to build target from sources
e.g.
game : main.o graphics.o world.o gcc -o game main.o graphics.o world.o
Rule: target is rebuilt if older than any sourcei
❖ ... Sidetrack: Make/Makefiles |
A Makefile
game : main.o graphics.o world.o gcc -o game main.o graphics.o world.o main.o : main.c graphics.h world.h gcc -Wall -Werror -std=c11 -c main.c graphics.o : graphics.c world.h gcc -Wall -Werror -std=c11 -c graphics.c world.o : world.c gcc -Wall -Werror -std=c11 -c world.c
Things to note:
game
main.o
:
gcc …
❖ ... Sidetrack: Make/Makefiles |
If make
make world.o gcc -Wall -Werror -std=c11 -c world.c
If no args, build first target in the Makefile
make gcc -Wall -Werror -std=c11 -c main.c gcc -Wall -Werror -std=c11 -c graphics.c gcc -Wall -Werror -std=c11 -c world.c gcc -o game main.o graphics.o world.o
❖ Exercise : Makefile |
Write a Makefile
❖ Summary |
malloc()
NULL
free()
malloc()
Produced: 20 Dec 2021