CS 202 - Notes 2018-11-07
The heap
If we want persistent memory (for example, so we can return an array from a function), we need to allocate it manually. We ask the system for a chunk of memory and it returns a pointer to the allocated chunk.
A downside to this is that we are responsible for memory management. We need to tell the system when we are done with the allocated memory, or it will be reserved for us until the program quits. This is called a memory leak. Some languages have compilers or interpreters that include a garbage collector, which can track down and eliminate these orphaned pieces of allocated memory.
Our main allocation function is malloc(size)
. malloc
takes in the number of bytes we would like and returns a pointer (sizeof()
is a handy function for determining the size of things). The pointer is a void *
, so we typically need to type cast it to an appropriate pointer type. We also have a convenience function for creating arrays called calloc(count, size)
. This does the same thing as malloc
, but will reserve count * size
bytes, so it can hold an array of length count
containing objects of size size
.
To release our data, we need to call free()
, passing it the pointer to the allocated memory.
Here is an example of calloc
in action in an alterative for of our rot13
assignment.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char * rot13(char * str){
int length = strlen(str);
char * newstr = (char *) calloc(length+1, sizeof(char));
newstr[length] ='\0'; // make sure our new string as a null terminator
for (int i = 0; i < length; i++){
char c = str[i];
if (c >= 'a' && c <= 'z'){
// this technique works by moving the base of the alphabet down to 0 so we can
// do modulo arithmetic
c = c - 'a'; // shift 'a' to 0
c = (c + 13) % 26; // add 13 with the wrap around if we go over
c = c + 'a'; // shift the alphabet back into place
}else if (c >= 'A' && c <= 'Z'){
// This uses the same math, but this time we will be clever
// and make use of the fact that 13 is half 26
c = ((c+13) <= 'Z') ? (c+13) : (c - 13);
}
newstr[i] = c;
}
return newstr;
}
int main(int argc, char * argv[]){
char * code = rot13(argv[1]);
printf("%s\n", code);
free(code);
}
Structs
The struct
in C is a way to make more complex data structures. It works a bit like an array, in that we have multiple pieces of data bundled together in memory that we can talk about as a single entity. However, unlike arrays, the data is heterogenous, and each field is labeled individually.
Creating a new color
type:
typedef unsigned char byte;
typedef struct {
byte red;
byte green;
byte blue;
float alpha;
} color;
Note that we used the typedef
command to create a name for our new type (and for a byte
type while we were at it).
To use our new type, we can just create new variables using it, accessing the fields using dot notation.
color c1;
c1.red = 255;
c1.green = 127;
c1. blue = 0;
c1.alpha = 1.0;
printf("red: %x, green: %x, blue: %x, alpha: %f\n", c1.red, c1.green, c1.blue, c1.alpha);
printf("red: %p, green: %p, blue: %p, alpha: %p\n", &c1.red, &c1.green, &c1.blue, &c1.alpha);
When we run this code, we get:
red: ff, green: 7f, blue: 0, alpha: 1.000000
red: 0x7fff41055ab0, green: 0x7fff41055ab1, blue: 0x7fff41055ab2, alpha: 0x7fff41055ab4
As we observed in class, we can see that the data is tightly packed together, but there is a gap between the blue and alpha fields.
We can write a simple function for creating new color structures, allocating them on the heap:
color * make_color(byte r, byte g, byte b, float a){
color * cp = (color *) malloc(sizeof(color));
(*cp).red = r;
cp->green = g;
cp->blue = b;
cp->alpha = a;
return cp;
}
Notice the two different syntaxes for accessing fields. Since cp
is a pointer, it needs to be dereferenced before we can access its fields. The conventional way to do this would be (*cp).red
, but this comes up so often, that we have some special syntax that just stands for "dereference and access the field": cp->red
.
color *c2 = make_color(0,127,255,1.0);
printf("red: %x, green: %x, blue: %x, alpha: %f\n", c2->red, c2->green, c2->blue, c2->alpha);
printf("red: %p, green: %p, blue: %p, alpha: %p\n", &c2->red, &c2->green, &c2->blue, &c2->alpha);
Unions
Unions are closely related to structs, and look identical in their construction. The difference is that their fields all point to the same location in memory. Here is a color unioned with an integer (I changed the color definition a little so that it only consumed four bytes).
typedef struct {
byte red;
byte green;
byte blue;
byte alpha;
} color_2;
typedef union{
color_2 c;
int i;
} color_3;
We can set the value with one field and read it out from another.
color_3 c3;
c3.i = 0x77007f00;
printf("red: %x, green: %x, blue: %x, alpha: %x\n", c3.c.red, c3.c.green, c3.c.blue, c3.c.alpha);
This prints
red: 0, green: 7f, blue: 0, alpha: 77
Note the effect of endianess on this.
More typically, we wouldn't use this to move between representations, but to create a space in a struct that could hold different types of data depending on context.
Full source
Here is the full source of the examples we did in class
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char byte;
typedef struct {
byte red;
byte green;
byte blue;
float alpha;
} color;
typedef struct {
byte red;
byte green;
byte blue;
byte alpha;
} color_2;
typedef union{
color_2 c;
int i;
} color_3;
color * make_color(byte r, byte g, byte b, float a){
color * cp = (color *) malloc(sizeof(color));
(*cp).red = r;
cp->green = g;
cp->blue = b;
cp->alpha = a;
return cp;
}
color * add_colors(color * c1, color* c2){
return make_color(c1->red+c2->red, c1->green+c2->green, c1->blue+c2->blue, c1->alpha+c2->alpha);
}
int main(int argc, char * argv[]){
color c1;
color *c2 = make_color(0,127,255,1.0);
color_3 c3;
c1.red = 255;
c1.green = 127;
c1. blue = 0;
c1.alpha = 1.0;
printf("red: %x, green: %x, blue: %x, alpha: %f\n", c1.red, c1.green, c1.blue, c1.alpha);
printf("red: %p, green: %p, blue: %p, alpha: %p\n", &c1.red, &c1.green, &c1.blue, &c1.alpha);
printf("red: %x, green: %x, blue: %x, alpha: %f\n", c2->red, c2->green, c2->blue, c2->alpha);
printf("red: %p, green: %p, blue: %p, alpha: %p\n", &c2->red, &c2->green, &c2->blue, &c2->alpha);
c3.i = 0x77007f00;
printf("red: %x, green: %x, blue: %x, alpha: %x\n", c3.c.red, c3.c.green, c3.c.blue, c3.c.alpha);
free(c2);
}