0% found this document useful (0 votes)
9 views

Lecture 8 & 9 generics

Uploaded by

epicfacegotyou
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views

Lecture 8 & 9 generics

Uploaded by

epicfacegotyou
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 123

C Generics – Void *

Instructor: Ayesha Anzer


Question: How can we use our
knowledge of memory and data
representation to write code that
works with any data type?
Learning Goals

Learn how to write C code that works with any data type.
Learn about how to use void * and avoid potential pitfalls.
Generics

• We always strive to write code that is as general-purpose


as possible.
• Generic code reduces code duplication and means you
can make improvements and fix bugs in one place rather
than many.
• Generics is used throughout C for functions to sort any
array, search any array, free arbitrary memory, and more.
• How can we write generic code in C?
Swap

You’re asked to write a function that swaps two


numbers.

void swap_int(int *a, int *b) {


int temp = *a;
*a = *b;
*b = temp;
}

int main(int argc, char *argv[]) {


int x = 2;
int y = 5;
swap_int(&x, &y);
// want x = 5, y = 2
printf("x = %d, y = %d\n", x, y);
return 0;
}
Swap

You’re asked to write a function that swaps two


numbers. Stack
Address Value
void swap_int(int *a, int *b) { …

int temp = *a; x 0xff14 2


*a = *b; main()
y 0xff10 5
*b = temp; …

int main(int argc, char *argv[]) {


int x = 2;
int y = 5;
swap_int(&x, &y);
// want x = 5, y = 2
printf("x = %d, y = %d\n", x, y);
return 0;
}
Swap

You’re asked to write a function that swaps two


numbers. Stack
Address Value
void swap_int(int *a, int *b) { …

int temp = *a; x 0xff14 2


*a = *b; main()
y 0xff10 5
*b = temp; …

} b 0xf18 0xff10
swap_int() a 0xf10 0xff14
int main(int argc, char *argv[]) { …

int x = 2;
int y = 5;
swap_int(&x, &y);
// want x = 5, y = 2
printf("x = %d, y = %d\n", x, y);
return 0;
}
Swap

You’re asked to write a function that swaps two


numbers. Stack
Address Value
void swap_int(int *a, int *b) { …

int temp = *a; x 0xff14 2


*a = *b; main()
y 0xff10 5
*b = temp; …

} b 0xf18 0xff10
swap_int() a 0xf10 0xff14
int main(int argc, char *argv[]) { temp 0xf0c 2
int x = 2; …
int y = 5;
swap_int(&x, &y);
// want x = 5, y = 2
printf("x = %d, y = %d\n", x, y);
return 0;
}
Swap

You’re asked to write a function that swaps two


numbers. Stack
Address Value
void swap_int(int *a, int *b) { …

int temp = *a; x 0xff14 5


*a = *b; main()
y 0xff10 5
*b = temp; …

} b 0xf18 0xff10
swap_int() a 0xf10 0xff14
int main(int argc, char *argv[]) { temp 0xf0c 2
int x = 2; …
int y = 5;
swap_int(&x, &y);
// want x = 5, y = 2
printf("x = %d, y = %d\n", x, y);
return 0;
}
Swap

You’re asked to write a function that swaps two


numbers. Stack
Address Value
void swap_int(int *a, int *b) { …

int temp = *a; x 0xff14 5


*a = *b; main()
y 0xff10 2
*b = temp; …

} b 0xf18 0xff10
swap_int() a 0xf10 0xff14
int main(int argc, char *argv[]) { temp 0xf0c 2
int x = 2; …
int y = 5;
swap_int(&x, &y);
// want x = 5, y = 2
printf("x = %d, y = %d\n", x, y);
return 0;
}
Swap

You’re asked to write a function that swaps two


numbers. Stack
Address Value

void swap_int(int *a, int *b) {
int temp = *a; x 0xff14 5
main()
*a = *b; y 0xff10 2
*b = temp; …

int main(int argc, char *argv[]) {


int x = 2;
int y = 5;
swap_int(&x, &y);
// want x = 5, y = 2
printf("x = %d, y = %d\n", x, y);
return 0;
}
Swap

You’re asked to write a function that swaps two


numbers. Stack
Address Value

void swap_int(int *a, int *b) {
int temp = *a; x 0xff14 5
main()
*a = *b; y 0xff10 2
*b = temp; …

int main(int argc, char *argv[]) {


int x = 2;
int y = 5;
swap_int(&x, &y);
// want x = 5, y = 2
printf("x = %d, y = %d\n", x, y);
return 0;
}
Swap

You’re asked to write a function that swaps two


numbers. Stack
Address Value

void swap_int(int *a, int *b) {
int temp = *a; x 0xff14 5
main()
*a = *b; y 0xff10 2
*b = temp; …

int main(int argc, char *argv[]) {


int x = 2;
int y = 5;
swap_int(&x, &y);
// want x = 5, y = 2
printf("x = %d, y = %d\n", x, y);
return 0;
}
“Oh, when I said ’numbers’ I meant
shorts, not ints.”
Swap

void swap_short(short *a, short *b) {


short temp = *a;
*a = *b;
*b = temp;
}

int main(int argc, char *argv[]) {


short x = 2;
short y = 5;
swap_short(&x, &y);
// want x = 5, y = 2
printf("x = %d, y = %d\n", x, y);
return 0;
}
Swap

void swap_short(short *a, short *b) { Stack


Address Value
short temp = *a; …
*a = *b;
*b = temp; x 0xff12 2
main()
} y 0xff10 5

int main(int argc, char *argv[]) { b 0xf18 0xff10


short x = 2; swap_short() a 0xf10 0xff12
short y = 5; temp 0xf0e 2

swap_short(&x, &y);
// want x = 5, y = 2
printf("x = %d, y = %d\n", x, y);
return 0;
}
“You know what, I goofed. We’re
going to use strings. Could you
write something to swap those?”
Swap

void swap_string(char **a, char **b) {


char *temp = *a;
*a = *b;
*b = temp;
}

int main(int argc, char *argv[]) {


char *x = "2";
char *y = "5";
swap_string(&x, &y);
// want x = 5, y = 2
printf("x = %s, y = %s\n", x, y);

return 0;
}
Swap

void swap_string(char **a, char **b) {


Address Value
char *temp = *a; …
*a = *b;
*b = temp; x 0xff18 0xc
main()
} y 0xff10 0xe

0xf '\0'
int main(int argc, char *argv[]) {
char *x = "2"; 0xe '5'
DATA SEGMENT 0xd '\0'
char *y = "5";
swap_string(&x, &y); 0xc '2'
// want x = 5, y = 2 …

printf("x = %s, y = %s\n", x, y);

return 0;
}
Swap

void swap_string(char **a, char **b) {


Address Value
char *temp = *a; …
*a = *b;
*b = temp; x 0xff18 0xc
main()
} y 0xff10 0xe

int main(int argc, char *argv[]) { b 0xf18 0xff10


char *x = "2"; swap_string() a 0xf10 0xff18

char *y = "5";
0xf '\0'
swap_string(&x, &y);
// want x = 5, y = 2 0xe '5'
printf("x = %s, y = %s\n", x, y); 0xd '\0'
DATA SEGMENT 0xc '2'
return 0; …

}
Swap

void swap_string(char **a, char **b) {


Address Value
char *temp = *a; …
*a = *b;
*b = temp; x 0xff18 0xc
main()
} y 0xff10 0xe

int main(int argc, char *argv[]) { b 0xf18 0xff10


char *x = "2"; swap_string() a 0xf10 0xff18
char *y = "5"; temp 0xf08 0xc

swap_string(&x, &y);
// want x = 5, y = 2 0xf '\0'
printf("x = %s, y = %s\n", x, y); 0xe '5'
DATA SEGMENT 0xd '\0'
return 0; 0xc '2'
} …
Swap

void swap_string(char **a, char **b) {


Address Value
char *temp = *a; …
*a = *b;
*b = temp; x 0xff18 0xe
main()
} y 0xff10 0xe

int main(int argc, char *argv[]) { b 0xf18 0xff10


char *x = "2"; swap_string() a 0xf10 0xff18
char *y = "5"; temp 0xf08 0xc

swap_string(&x, &y);
// want x = 5, y = 2 0xf '\0'
printf("x = %s, y = %s\n", x, y); 0xe '5'
DATA SEGMENT 0xd '\0'
return 0; 0xc '2'
} …
Swap

void swap_string(char **a, char **b) {


Address Value
char *temp = *a; …
*a = *b;
*b = temp; x 0xff18 0xe
main()
} y 0xff10 0xc

int main(int argc, char *argv[]) { b 0xf18 0xff10


char *x = "2"; swap_string() a 0xf10 0xff18
char *y = "5"; temp 0xf08 0xc

swap_string(&x, &y);
// want x = 5, y = 2 0xf '\0'
printf("x = %s, y = %s\n", x, y); 0xe '5'
DATA SEGMENT 0xd '\0'
return 0; 0xc '2'
} …
Swap

void swap_string(char **a, char **b) {


Address Value
char *temp = *a; …
*a = *b;
*b = temp; x 0xff18 0xe
main()
} y 0xff10 0xc

0xf '\0'
int main(int argc, char *argv[]) {
char *x = "2"; 0xe '5'
DATA SEGMENT 0xd '\0'
char *y = "5";
swap_string(&x, &y); 0xc '2'
// want x = 5, y = 2 …

printf("x = %s, y = %s\n", x, y);

return 0;
}
Swap

void swap_string(char **a, char **b) {


Address Value
char *temp = *a; …
*a = *b;
*b = temp; x 0xff18 0xe
main()
} y 0xff10 0xc

0xf '\0'
int main(int argc, char *argv[]) {
char *x = "2"; 0xe '5'
DATA SEGMENT 0xd '\0'
char *y = "5";
swap_string(&x, &y); 0xc '2'
// want x = 5, y = 2 …

printf("x = %s, y = %s\n", x, y);

return 0;
}
Swap

void swap_string(char **a, char **b) {


Address Value
char *temp = *a; …
*a = *b;
*b = temp; x 0xff18 0xe
main()
} y 0xff10 0xc

0xf '\0'
int main(int argc, char *argv[]) {
char *x = "2"; 0xe '5'
DATA SEGMENT 0xd '\0'
char *y = "5";
swap_string(&x, &y); 0xc '2'
// want x = 5, y = 2 …

printf("x = %s, y = %s\n", x, y);

return 0;
}
“Awesome! Thanks. We also have
20 custom struct types. Could you
write swap for those too?”
Generic Swap

What if we could write one function to swap two values of any single
type?

void swap_int(int *a, int *b) { … }


void swap_float(float *a, float *b) { … }
void swap_size_t(size_t *a, size_t *b) { … }
void swap_double(double *a, double *b) { … }
void swap_string(char **a, char **b) { … }
void swap_mystruct(mystruct *a, mystruct *b) { … }

Generic Swap

void swap_int(int *a, int *b) {


int temp = *a;
*a = *b;
*b = temp;
}

void swap_short(short *a, short *b) {


short temp = *a;
*a = *b;
*b = temp;
}

void swap_string(char **a, char **b) {


char *temp = *a;
*a = *b;
*b = temp;
}
Generic Swap

void swap_int(int *a, int *b) {


int temp = *a;
*a = *b; All 3:
*b = temp;
• Take pointers to values to
}
swap
void swap_short(short *a, short *b) { • Create temporary storage to
short temp = *a; store one of the values
*a = *b; • Move data at b into where a
*b = temp; points
}
• Move data in temporary
void swap_string(char **a, char **b) { storage into where b points
char *temp = *a;
*a = *b;
*b = temp;
}
Generic Swap

void swap(pointer to data1, pointer to data2) {


store a copy of data1 in temporary storage
copy data2 to location of data1
copy data in temporary storage to location of data2
}
Generic Swap

void swap(pointer to data1, pointer to data2) {


store a copy of data1 in temporary storage
copy data2 to location of data1
copy data in temporary storage to location of data2
}

4 bytes int temp = *data1ptr;

2 bytes short temp = *data1ptr;

8 bytes char *temp = *data1ptr;

Problem: each type may need a different size temp! (modern machine * = 8B)
Generic Swap

void swap(pointer to data1, pointer to data2) {


store a copy of data1 in temporary storage
copy data2 to location of data1
copy data in temporary storage to location of data2
}

4 bytes int temp = *data1ptr;

2 bytes short temp = *data1ptr;

8 bytes char *temp = *data1ptr;

Problem: each type needs to copy a different amount of data!


Generic Swap

void swap(pointer to data1, pointer to data2) {


store a copy of data1 in temporary storage
copy data2 to location of data1
copy data in temporary storage to location of data2
}

4 bytes int temp = *data1ptr;

2 bytes short temp = *data1ptr;

8 bytes char *temp = *data1ptr;

Problem: each type needs to copy a different amount of data!


C knows the size of temp, and
knows how many bytes to copy,
because of the variable types.
Is there a way to make a version
that doesn’t care about the variable
types?
Generic Swap

void swap(pointer to data1, pointer to data2) {


store a copy of data1 in temporary storage
copy data2 to location of data1
copy data in temporary storage to location of data2
}
Generic Swap

void swap(pointer to data1, pointer to data2) {


store a copy of data1 in temporary storage
copy data2 to location of data1
copy data in temporary storage to location of data2
}
Generic Swap

void swap(void *data1ptr, void *data2ptr) {


store a copy of data1 in temporary storage
copy data2 to location of data1
copy data in temporary storage to location of data2
}
Generic Swap

void swap(void *data1ptr, void *data2ptr) {


// store a copy of data1 in temporary storage
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}
Generic Swap

void swap(void *data1ptr, void *data2ptr) {


// store a copy of data1 in temporary storage
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}

If we don’t know the data type, we don’t know


how many bytes it is. Let’s take that as another
parameter.
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


// store a copy of data1 in temporary storage
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}

If we don’t know the data type, we don’t know


how many bytes it is. Let’s take that as another
parameter.
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


// store a copy of data1 in temporary storage
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}

Let’s start by making space to store the temporary


value. How can we make nbytes of temp space?
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


void temp; ???
// store a copy of data1 in temporary storage
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}

Let’s start by making space to store the temporary


value. How can we make nbytes of temp space?
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}

temp is nbytes of memory,


since each char is 1 byte!
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}

Now, how can we copy in what


data1ptr points to into temp?
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
temp = *data1ptr; ???
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}

Now, how can we copy in what


data1ptr points to into temp?
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
temp = *data1ptr; ???
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}

We can’t dereference a void * (or set an array


equal to something). C doesn’t know what it
points to! Therefore, it doesn’t know how many
bytes there it should be looking at.
memcpy

memcpy is a function that copies a specified amount of bytes at one


address to another address.

void *memcpy(void *dest, const void *src, size_t n);

It copies the next n bytes that src points to to the location contained in
dest. (It also returns dest). It does not support regions of memory
that overlap.
memcpy must take pointers to the bytes to work with to
int x = 5;
know where they live and where they should be copied to.
int y = 4;
memcpy(&x, &y, sizeof(x)); // like x = y
memmove

memmove is the same as memcpy, but supports overlapping


regions of memory. (Unlike its name implies, it still “copies”).

void *memmove(void *dest, const void *src, size_t n);

It copies the next n bytes that src points to to the location contained
in dest. (It also returns dest).
memmove

When might memmove be useful?

1 2 3 4 5 6 7

4 5 6 7 5 6 7
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
temp = *data1ptr; ???
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}

We can’t dereference a void *. C doesn’t know


what it points to! Therefore, it doesn’t know how
many bytes there it should be looking at.
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
temp = *data1ptr; ???
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}
How can memcpy or memmove help us here?

void *memcpy(void *dest, const void *src, size_t n);

void *memmove(void *dest, const void *src, size_t n);


Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
memcpy(temp, data1ptr, nbytes);
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
memcpy(temp, data1ptr, nbytes);
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}

We can copy the bytes ourselves into temp! This


is equivalent to temp = *data1ptr in non-generic
versions, but this works for any type of any size.
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
memcpy(temp, data1ptr, nbytes);
// copy data2 to location of data1
// copy data in temporary storage to location of data2
}

How can we copy data2 to the location of data1?


Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
memcpy(temp, data1ptr, nbytes);
// copy data2 to location of data1
*data1ptr = *data2ptr; ???
// copy data in temporary storage to location of data2
}
How can we copy data2 to the location of data1?
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
memcpy(temp, data1ptr, nbytes);
// copy data2 to location of data1
memcpy(data1ptr, data2ptr, nbytes);
// copy data in temporary storage to location of data2
}
How can we copy data2 to the location of data1?
memcpy!
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
memcpy(temp, data1ptr, nbytes);
// copy data2 to location of data1
memcpy(data1ptr, data2ptr, nbytes);
// copy data in temporary storage to location of data2
}
How can we copy temp’s data to the location of
data2?
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
memcpy(temp, data1ptr, nbytes);
// copy data2 to location of data1
memcpy(data1ptr, data2ptr, nbytes);
// copy data in temporary storage to location of data2
memcpy(data2ptr, temp, nbytes);
} How can we copy temp’s data to the location of
data2? memcpy!
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
memcpy(temp, data1ptr, nbytes);
// copy data2 to location of data1
memcpy(data1ptr, data2ptr, nbytes);
// copy data in temporary storage to location of data2
memcpy(data2ptr, temp, nbytes);
}

int x = 2;
int y = 5;
swap(&x, &y, sizeof(x));
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
memcpy(temp, data1ptr, nbytes);
// copy data2 to location of data1
memcpy(data1ptr, data2ptr, nbytes);
// copy data in temporary storage to location of data2
memcpy(data2ptr, temp, nbytes);
}

short x = 2;
short y = 5;
swap(&x, &y, sizeof(x));
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
memcpy(temp, data1ptr, nbytes);
// copy data2 to location of data1
memcpy(data1ptr, data2ptr, nbytes);
// copy data in temporary storage to location of data2
memcpy(data2ptr, temp, nbytes);
}

char *x = "2";
char *y = "5";
swap(&x, &y, sizeof(x));
Generic Swap

void swap(void *data1ptr, void *data2ptr, size_t nbytes) {


char temp[nbytes];
// store a copy of data1 in temporary storage
memcpy(temp, data1ptr, nbytes);
// copy data2 to location of data1
memcpy(data1ptr, data2ptr, nbytes);
// copy data in temporary storage to location of data2
memcpy(data2ptr, temp, nbytes);
}

mystruct x = {…};
mystruct y = {…};
swap(&x, &y, sizeof(x));
C Generics

We can use void * and memcpy to handle memory as generic bytes.


If we are given where the data of importance is, and how big it is, we
can handle it!

void swap(void *data1ptr, void *data2ptr, size_t nbytes)


{
char temp[nbytes];
memcpy(temp, data1ptr, nbytes);
memcpy(data1ptr, data2ptr, nbytes);
memcpy(data2ptr, temp, nbytes);
}
Void * Pitfalls

void *s are powerful, but dangerous - C cannot do as much


checking!
E.g. with int, C would never let you swap half of an int. With
void *s, this can happen! (How? Let’s find out!)
Void *Pitfalls

Void * has more room for error because it manipulates arbitrary bytes
without knowing what they represent. This can result in some
strange memory Frankensteins!
Swap Ends

You’re asked to write a function that swaps the first and last elements in
an array of numbers.

void swap_ends_int(int *arr, size_t nelems) {


int tmp = arr[0];
arr[0] = arr[nelems – 1]; Wait – we just wrote a generic
arr[nelems – 1] = tmp; swap function. Let’s use that!
}

int main(int argc, char *argv[]) {


int nums[] = {5, 2, 3, 4, 1};
size_t nelems = sizeof(nums) / sizeof(nums[0]);
swap_ends_int(nums, nelems);
// want nums[0] = 1, nums[4] = 5
printf("nums[0] = %d, nums[4] = %d\n", nums[0], nums[4]);
return 0;
}
Swap Ends

You’re asked to write a function that swaps the first and last elements in
an array of numbers.

void swap_ends_int(int *arr, size_t nelems) {


swap(arr, arr + nelems – 1, sizeof(*arr));
} Wait – we just wrote a generic
swap function. Let’s use that!
int main(int argc, char *argv[]) {
int nums[] = {5, 2, 3, 4, 1};
size_t nelems = sizeof(nums) / sizeof(nums[0]);
swap_ends_int(nums, nelems);
// want nums[0] = 1, nums[4] = 5
printf("nums[0] = %d, nums[4] = %d\n", nums[0], nums[4]);
return 0;
}
Swap Ends

Let’s write out what some other versions would look like (just in case).

void swap_ends_int(int *arr, size_t nelems) {


swap(arr, arr + nelems – 1, sizeof(*arr));
}

void swap_ends_short(short *arr, size_t nelems) {


swap(arr, arr + nelems – 1, sizeof(*arr));
}

void swap_ends_string(char **arr, size_t nelems) {


swap(arr, arr + nelems – 1, sizeof(*arr));
} The code seems to be the
same regardless of the type!
void swap_ends_float(float *arr, size_t nelems) {
swap(arr, arr + nelems – 1, sizeof(*arr));
}
Swap Ends

Let’s write a version of swap_ends that works for any type of array.

void swap_ends(void *arr, size_t nelems) {


swap(arr, arr + nelems – 1, sizeof(*arr));
}

Is this generic? Does this work?

Unfortunately not. Firs,t we no longer know the


element size. Second, pointer arithmetic depends
on the type of data being pointed to. With a void *,
we lose that information!
Swap Ends

Let’s write a version of swap_ends that works for any type of array.

void swap_ends(void *arr, size_t nelems) {


swap(arr, arr + nelems – 1, sizeof(*arr));
}

Is this generic? Does this work?

Unfortunately not. First, we no longer know the


element size. Second, pointer arithmetic depends
on the type of data being pointed to. With a void *,
we lose that information!
Swap Ends

Let’s write a version of swap_ends that works for any type of array.

void swap_ends(void *arr, size_t nelems) {


swap(arr, arr + nelems – 1, sizeof(*arr));
}

We need to know the element size, so


let’s add a parameter.
Swap Ends

Let’s write a version of swap_ends that works for any type of array.

void swap_ends(void *arr, size_t nelems, size_t elem_bytes) {


swap(arr, arr + nelems – 1, elem_bytes);
}

We need to know the element size, so


let’s add a parameter.
Pointer Arithmetic

arr + nelems – 1

Let’s say nelems = 4. How many bytes beyond arr is this?

If it’s an array of…


Int?
Pointer Arithmetic

arr + nelems – 1

Let’s say nelems = 4. How many bytes beyond arr is this?

If it’s an array of…


Int: adds 3 places to arr, and 3 * sizeof(int) = 12 bytes
Pointer Arithmetic

arr + nelems – 1

Let’s say nelems = 4. How many bytes beyond arr is this?

If it’s an array of…


Int: adds 3 places to arr, and 3 * sizeof(int) = 12 bytes
Short?
Pointer Arithmetic

arr + nelems – 1

Let’s say nelems = 4. How many bytes beyond arr is this?

If it’s an array of…


Int: adds 3 places to arr, and 3 * sizeof(int) = 12 bytes
Short: adds 3 places to arr, and 3 * sizeof(short) = 6 bytes
Pointer Arithmetic

arr + nelems – 1

Let’s say nelems = 4. How many bytes beyond arr is this?

If it’s an array of…


Int: adds 3 places to arr, and 3 * sizeof(int) = 12 bytes
Short: adds 3 places to arr, and 3 * sizeof(short) = 6 bytes
Char *: adds 3 places to arr, and 3 * sizeof(char *) = 24 bytes

In each case, we need to know the element size to do the arithmetic.


Swap Ends

Let’s write a version of swap_ends that works for any type of array.

void swap_ends(void *arr, size_t nelems, size_t elem_bytes) {


swap(arr, arr + nelems – 1, elem_bytes);
}

How many bytes past arr should we go to


get to the last element?

(nelems – 1) * elem_bytes
Swap Ends

Let’s write a version of swap_ends that works for any type of array.

void swap_ends(void *arr, size_t nelems, size_t elem_bytes) {


swap(arr, arr + (nelems – 1) * elem_bytes, elem_bytes);
}

How many bytes past arr should we go to


get to the last element?

(nelems – 1) * elem_bytes
Swap Ends

Let’s write a version of swap_ends that works for any type of array.

void swap_ends(void *arr, size_t nelems, size_t elem_bytes) {


swap(arr, arr + (nelems – 1) * elem_bytes, elem_bytes);
}

But C still can’t do arithmetic with a


void*. We need to tell it to not worry
about it, and just add bytes. How can we
do this?
Swap Ends

Let’s write a version of swap_ends that works for any type of array.

void swap_ends(void *arr, size_t nelems, size_t elem_bytes) {


swap(arr, (char *)arr + (nelems – 1) * elem_bytes, elem_bytes);
}

But C still can’t do arithmetic with a


void*. We need to tell it to not worry
about it, and just add bytes. How can we
do this?

char * pointers already add bytes!


Swap Ends

You’re asked to write a function that swaps the first and last elements in
an array of numbers. Well, now it can swap for an array of anything!

void swap_ends(void *arr, size_t nelems, size_t elem_bytes) {


swap(arr, (char *)arr + (nelems – 1) * elem_bytes, elem_bytes);
}
Swap Ends

You’re asked to write a function that swaps the first and last elements in
an array of numbers. Well, now it can swap for an array of anything!

void swap_ends(void *arr, size_t nelems, size_t elem_bytes) {


swap(arr, (char *)arr + (nelems – 1) * elem_bytes, elem_bytes);
}

int nums[] = {5, 2, 3, 4, 1};


size_t nelems = sizeof(nums) / sizeof(nums[0]);
swap_ends(nums, nelems, sizeof(nums[0]));
Swap Ends

You’re asked to write a function that swaps the first and last elements in
an array of numbers. Well, now it can swap for an array of anything!

void swap_ends(void *arr, size_t nelems, size_t elem_bytes) {


swap(arr, (char *)arr + (nelems – 1) * elem_bytes, elem_bytes);
}

short nums[] = {5, 2, 3, 4, 1};


size_t nelems = sizeof(nums) / sizeof(nums[0]);
swap_ends(nums, nelems, sizeof(nums[0]));
Swap Ends

You’re asked to write a function that swaps the first and last elements in
an array of numbers. Well, now it can swap for an array of anything!

void swap_ends(void *arr, size_t nelems, size_t elem_bytes) {


swap(arr, (char *)arr + (nelems – 1) * elem_bytes, elem_bytes);
}

char *strs[] = {"Hi", "Hello", "Howdy"};


size_t nelems = sizeof(strs) / sizeof(strs[0]);
swap_ends(strs, nelems, sizeof(strs[0]));
Swap Ends

You’re asked to write a function that swaps the first and last elements in
an array of numbers. Well, now it can swap for an array of anything!

void swap_ends(void *arr, size_t nelems, size_t elem_bytes) {


swap(arr, (char *)arr + (nelems – 1) * elem_bytes, elem_bytes);
}

mystruct structs[] = …;
size_t nelems = …;
swap_ends(structs, nelems, sizeof(structs[0]));
Stacks

A Stack is a data structure representing


a stack of things.
Objects can be pushed on top of or
popped from the top of the stack.
Only the top of the stack can be
accessed; no other objects in the stack push pop, peek
are visible.
top 31
Main operations:
2
push(value): add an element to bottom 10
the top of the stack stack
pop(): remove and return the top
element in the stack
peek(): return (but do not remove)
the top element in the stack
What modifications are necessary
to make a generic stack?
Stack Structs

typedef struct int_node {


struct int_node *next;
int data;
} int_node;
How might we modify the Stack data
representation itself to be generic?
typedef struct int_stack {
int nelems;
int_node *top;
} int_stack;
Stack Structs

typedef struct int_node {


struct int_node *next;
int data;
} int_node;

typedef struct int_stack {


int nelems;
int_node *top;
} int_stack;

Problem: each node can no longer store the


data itself, because it could be any size!
Generic Stack Structs

typedef struct int_node {


struct int_node *next;
void *data;
} int_node;

typedef struct stack {


int nelems;
int elem_size_bytes;
node *top;
} stack;

Solution: each node stores a pointer, which is


always 8 bytes, to the data somewhere else. We
must also store the data size in the Stack struct.
Stack Functions

int_stack_create(): creates a new stack on the heap


and returns a pointer to it
int_stack_push(int_stack *s, int data): pushes
data onto the stack
int_stack_pop(int_stack *s): pops and returns topmost
stack element
int_stack_create

int_stack *int_stack_create() {
int_stack *s = malloc(sizeof(int_stack));
s->nelems = 0;
s->top = NULL;
return s; How might we modify this function to be
} generic?

From previous slide:


typedef struct stack {
int nelems;
int
elem_size_bytes;
node *top;
} stack;
Generic stack_create

stack *stack_create(int elem_size_bytes) {


stack *s = malloc(sizeof(stack));
s->nelems = 0;
s->top = NULL;
s->elem_size_bytes = elem_size_bytes;
return s;
}
int_stack_push

void int_stack_push(int_stack *s, int data) {


int_node *new_node = malloc(sizeof(int_node));
new_node->data = data;

new_node->next = s->top; How might we modify this function to be


s->top = new_node; generic?
s->nelems++;
}

From previous slide:


typedef struct stack { typedef struct node
int nelems; {
int struct node
elem_size_bytes; *next;
node *top; void *data;
} stack; } node;
Generic stack_push

void int_stack_push(int_stack *s, int data) {


int_node *new_node = malloc(sizeof(int_node));
new_node->data = data;

new_node->next = s->top;
s->top = new_node;
s->nelems++;
}

Problem 1: we can no longer pass the data itself


as a parameter, because it could be any size!
Generic stack_push

void int_stack_push(int_stack *s, void *data) {


int_node *new_node = malloc(sizeof(int_node));
new_node->data = data;

new_node->next = s->top;
s->top = new_node;
s->nelems++;
}

Solution 1: pass a pointer to the


data as a parameter instead.
Generic stack_push

void int_stack_push(int_stack *s, void *data) {


int_node *new_node = malloc(sizeof(int_node));
new_node->data = data;

new_node->next = s->top;
s->top = new_node;
s->nelems++;
}
Problem 2: we cannot copy the existing data pointer
into new_node. The data structure must manage its
own copy that exists for its entire lifetime. The
provided copy may go away!
Generic stack_push

void stack_push(stack *s, void *data) {


node *new_node = malloc(sizeof(node));
new_node->data = malloc(s->elem_size_bytes);
memcpy(new_node->data, data, s->elem_size_bytes);

new_node->next = s->top;
s->top = new_node;
s->nelems++;
}
Solution 2: make a heap-allocated copy
of the data that the node points to.
int_stack_pop

int int_stack_pop(int_stack *s) {


if (s->nelems == 0) {
error(1, 0, "Cannot pop from empty stack");
}
int_node *n = s->top; How might we modify this function to be
generic?
int value = n->data;

s->top = n->next;
From previous slide:
typedef struct stack { typedef struct node
free(n); int nelems; {
s->nelems--; int struct node
elem_size_bytes; *next;
node *top; void *data;
return value; } stack; } node;

}
Generic stack_pop

int int_stack_pop(int_stack *s) {


if (s->nelems == 0) {
error(1, 0, "Cannot pop from empty stack");
}
int_node *n = s->top;
int value = n->data;

s->top = n->next;

free(n); Problem: we can no longer return the


s->nelems--; data itself, because it could be any size!

return value;
}
Generic stack_pop

void *int_stack_pop(int_stack *s) {


if (s->nelems == 0) {
error(1, 0, "Cannot pop from empty stack");
}
int_node *n = s->top;
void *value = n->data;
While it’s possible to return the heap
s->top = n->next; address of the element, this means the
client would be responsible for freeing it.
free(n); Ideally, the data structure should manage
s->nelems--; its own memory here.

return value;
}
Generic stack_pop

void stack_pop(stack *s, void *addr) {


if (s->nelems == 0) {
error(1, 0, "Cannot pop from empty stack");
}
node *n = s->top;
memcpy(addr, n->data, s->elem_size_bytes);
s->top = n->next;

free(n->data); Solution: have the caller pass a memory


free(n); location as a parameter and copy the data
s->nelems--; to that location.
}
Using Generic Stack

int_stack *intstack = int_stack_create();


for (int i = 0; i < TEST_STACK_SIZE; i++) {
int_stack_push(intstack, i);
}

We must now pass the address of an element to push


onto the stack, rather than the element itself.
Using Generic Stack

stack *intstack = stack_create(sizeof(int));


for (int i = 0; i < TEST_STACK_SIZE; i++) {
stack_push(intstack, &i);
}

We must now pass the address of an element to push


onto the stack, rather than the element itself.
Using Generic Stack

int_stack *intstack = int_stack_create();


int_stack_push(intstack, 7);

We must now pass the address of an element to push


onto the stack, rather than the element itself.
Using Generic Stack

stack *intstack = stack_create(sizeof(int));


int num = 7;
stack_push(intstack, &num);

We must now pass the address of an element to push


onto the stack, rather than the element itself.
Using Generic Stack

// Pop off all elements


while (intstack->nelems > 0) {
printf("%d\n", int_stack_pop(intstack));
}

We must now pass the address of where we would


like to store the popped element, rather than getting
it directly as a return value.
Using Generic Stack

// Pop off all elements


int popped_int;
while (intstack->nelems > 0) {
int_stack_pop(intstack, &popped_int);
printf("%d\n", popped_int);
}

We must now pass the address of where we would


like to store the popped element, rather than getting
it directly as a return value.
More efficient generic stack

typedef struct stack { typedef struct node {


size_t nelems; struct node *next;
size_t elem_size_bytes; void *data;
node *top; } node;
} stack;

☺  

top data data data



elem_size_bytes next next next
0x800 nelems 3

stack *
emojistack
0x800
More efficient generic stack

typedef struct stack { typedef struct node {


size_t nelems; struct node *next;
size_t elem_size_bytes; void *data;
node *top; } node;
} stack;

☺  

top data data data


elem_size_bytes
… next next next
0x800 nelems 3
Isn’t it better to store the data directly inside the node?
stack * • Why can’t we do this with the current implementation?
emojistack
0x800 • How could we modify our stack abstraction + functions
to do this?
More efficient generic stack

typedef struct stack { If we remove the node struct:


size_t nelems; We create nodes that are
size_t elem_size_bytes;
void *top; elem_size_bytes + 4B and directly
} stack; store the data into our node.

A “node” just becomes contiguous


top ☺ bytes of memory storing

elem_size_bytes (1) address of next node, and (2)
0x800 nelems 3 data

stack *
0x800 Tricky! We will be working with
emojistack
sizeof(void *) and (void **)!!
More efficient generic stack

typedef struct stack {


size_t nelems;
size_t elem_size_bytes;
void *top;
} stack;

Rewrite our generic_stack.c code without the node struct


Rewrite (as needed):
stack_create
stack_push
stack_pop
(Don’t touch main—a user of our stack should not know the
difference)
stack_create

typedef struct stack {


size_t nelems;
size_t elem_size_bytes;
void *top;
} stack;

1 stack *stack_create(size_t elem_size_bytes) {


2 stack *s = malloc(sizeof(stack));
3 s->nelems = 0;
4 s->top = NULL;
5 s->elem_size_bytes = elem_size_bytes;
6 return s;
7 } No nodes touched,
nothing to change
Old stack_push

1 void stack_push(stack *s, void *data) {


2 node *new_node = malloc(sizeof(node));
3 new_node->data = malloc(s->elem_size_bytes);
4 memcpy(new_node->data, data, s->elem_size_bytes);
5 new_node->next = s->top;
6 s->top = new_node;
7 s->nelems++;
8 } What do we have to change from the
old function? Check all functionality:
1. Allocate a node
2. Copy in data
3. Set new node’s next to be top of
stack
4. Set top of stack to be new node
5. Increment element count
1. Allocate a node

top 
stack * 0x800
emojistack

elem_size_bytes

0x800 nelems 2

In stack_push, we had: node *new_node = malloc(sizeof(node));

We no longer have a typedef struct node!


Our node is now just contiguous bytes on the heap.
How do we rewrite this line to handle
our new node representation?
1. Allocate a node

stack *
top 
0x800 …
elem_size_bytes
emojistack
0x800 nelems 3

In stack_push, we had: node *new_node = malloc(sizeof(node));

We no longer have a typedef struct node!


Our node is now just contiguous bytes on the heap.
How do we rewrite this line to handle our new node representation?

void *new_node = malloc(sizeof(void *) + s->elem_size_bytes);


New stack_push

1 void stack_push(stack *s, void *data) {


2 void *new_node = malloc(sizeof(void *) + s->elem_size_bytes);
3 memcpy((char *) new_node + sizeof(void *),
data, s->elem_size_bytes);
4 *((void **) new_node) = s->top;
5 s->top = new_node;
6 s->nelems++;
7 }
Check all functionality:
1.Allocate a node
2.Copy in data
3.Set new node’s next to be top
of stack
4.Set top of stack to be new
node
5.Increment element count
New stack_push

• sizeof(void *) is the size of a pointer, which is


always 4B in our class
• The dereference operation *(void **) ptr works!
• void * ptr = …; Declaration: ptr stores an
address, no idea what is at the address ptr
• (void **) ptr; Cast: at the address ptr, there is
an address
• *(void **) ptr; Dereference: get the address
stored at the address ptr
Old stack_pop

1 void stack_pop(stack *s, void *addr) {


2 if (s->nelems == 0) {
3 exit(1);
4 }
5 node *n = s->top;
6 memcpy(addr, n->data, s->elem_size_bytes);
7 s->top = n->next;
8
What do we have to change
free(n->data);
9 free(n); from the old function? Check
10 s->nelems--; all functionality:
11 } 1.Copy top node’s data to addr
buf
2.Set top of stack to top node’s
next
3.Free old top node
4.Decrement element count
New stack_pop

1 void stack_pop(stack *s, void *addr) {


2 if (s->nelems == 0) {
3 exit(1);
4 }
5 void *n = s->top;
6 memcpy(addr, (char *) n + sizeof(void *), s->elem_size_bytes);
7 s->top = *(void **) n;
8 free(n);
9 s->nelems--;
Check all functionality:
10 } 1.Copy top node’s data to addr
buf
2.Set top of stack to top node’s
next
3.Free old top node
4.Decrement element count

You might also like