Lecture 8: Generics
CS107 Topic 4: How can we use our knowledge of memory and data representation to write code that works with any data type?
Table of contents:
- What happens if we want to write a general purpose function that works with all data types (e.g. swap, binary sort)? We need to use types in the first place so that C knows how many bytes to allocate in memory. But to accomplish our goal, we introduce a new thing called generic pointers. When we allocate, say, an int on the stack frame, the C compiler knows to allocate 4 bytes. But when we do not know the size (i.e. generic), we must explicitly tell the compiler how many bytes to allocate. That is the theme for generics.
- We can make a pointer with no type designation using
void *
(pointer to "something").- But C does not know how many bytes we should allocate at the pointed location. We must manually tell C how many we want/need.
- To get around this, we first declare an array of type char for the amount of bytes we need. We use char as the type because a char is perfectly 1 byte!
This is best explained via examples:
Example
memcpy
- Function:
void *memcpy(void *dest, const void *src, size_t n);
- It copies the next bytes that
src
points to to the location contained indest
. (It also returns dest).- It does not support regions of memory that overlap.
- Basically, it goes to
src
, picks out bytes and tosses them into the locationdest
.
- Only takes pointers
- Cannot deference a
void *
pointer because it doesn't know how many bytes to get.
So what happens if we have overlapping regions? We use...
memmove
- Same as
memcpy
except it is for overlapping memory.
Pitfalls
With great power comes great responsibility
- No longer have the C compiler checking the types for us and ensuring we are using them correctly.
- We can do things like swap half of the bit representation if we pass in the wrong size.
Swap ends
- To get num bytes in an array:
size_t nelems = sizeof(nums) / sizeof(nums[0]);
.
Example
Pointer arithmetic
- We theoretically could not do pointer arithmetic with
void *
's since C does not know how many bytes to add/subtract since there is no type. Thus, we must tell C how many bytes the pointer should skip to. We can do this via:
(char *)arr + (num_elems * size_of_each_elem)
This makes sense. We also ne
ed to cast to a char *
first since a char is one byte and we can do pointer arithmetic with them (unlike void *
's).
- Of course, the same pitfalls discussed above also apply here.
Example
Recap
- void * is a variable type that represents a generic pointer “to something”.
- We cannot perform pointer arithmetic with or dereference a void *.
- We can use memcpy or memmove to copy data from one memory location to another.
- To do pointer arithmetic with a void *, we must first cast it to a char *.
- void * and generics are powerful but dangerous because of the lack of type checking, so we must be extra careful when working with generic memory.