Lecture 11

Administrivia

Start thinking about “signature contributions” to the wiki: one or two projects of ambition that you can work on, and report to the class.

Note that complete notes for Lecture-12 (Monday's lecture) are not posted, and will not be posted. If you want the content, come to class.

The C Programming Language, III

The C Type System

Integral types

Standard operators on integral types

Floating point

Complex (c99's _Complex, include <complex.h>)

Pointers

What is a pointer? It is simply an address associated with a type. The type matters.

An important point—C's function call semantics are call-by-value, which means that the arguments to a C function are local variables, initialized before the body of the function executes, and not names for storage associated with the call site. Can we write a C function that has a side-effect? Yes, via pointers:

void increment(int *ip) { ++*ip; } ... int a = 1; increment(&a); printf("%d\n",a); // prints "2". ...

What's happening here? Increment receives its own copy of the pointer to a. Increment doesn't change the pointer (ip), instead it uses that point to change the value in the storage pointed to.

Pointers turn out to be the key to dynamic storage allocation in C. Note that the type of a string is a pointer to a character (char *). This will become less mysterious soon.

C++ programmers, if any, should note that C doesn't have a reference type; as a reference type, nor does it need it. A C++ reference is basically syntactic sugar for pointers, and C is somewhat adverse to sugar.

The scanf(3) function—this can be thought of as something of an inverse to printf:

int a; scanf("%d",&a); // read an integer from standard input

scanf is very useful for handling trusted data. It should not be used if the data cannot be trusted, a common case. Note that scanf needs to take pointers as arguments, so it knows where to put the results.

Structs (product types)

Often, we want to create a data object that is comprised of multiple parts. For example, imagine a particle in a physics simulation. Relevant properties might be mass, position, and velocity. We can create a new data type that embodies all of this:

struct particle_store { double mass; double xpos, ypos; double xvel, yvel; };

Note the semicolon following the closed set brace. This is because the struct declaration can be combined with the definition of variables of that type, e.g.,

struct particle_store { double mass; double xpos, ypos; double xvel, yvel; } a, b, c;

defines variables a, b, and c of type struct particle_store. We can also separate the declaration of the type from the definition of the variables via:

struct particle_store a, b, c;

Note that this is different from C++, the struct is mandatory in C.

Typedefs are an important bit of syntactic sugar. We can create aliases for types using the typedef declarations, e.g.,

typedef struct particle_store *particle, particle_store;

The syntax after the typedef looks like we're defining two variables, whereas in fact we are defining two new type names, particle, and particle_store. Henceforth, we can use

particle a, b, c; sizeof(particle_store);

Note that the effect of this section definition is quite different from the forgoing, in that a, b, and c are now pointers, rather than structures.

Often, typedef is combined with the struct definition:

typedef struct particle_store { double mass; double xpos, ypos; double xvel, yvel; } particle_store, *particle;

Note that the first occurrence of particle_store here isn't necessary, because the struct has no struct particle_store * fields. As such, it would often be omitted in practice, i.e.,

typedef struct { double mass; double xpos, ypos; double xvel, yvel; } particle_store, *particle;

Typedefs are useful in C, and function much like the type keyword in Haskell. They're life and death in C++.

We refer to individual components of a structure by the dot notation:

particle_store a; a.xvel = 0;

refers to the xvel field of a. Assignment involving structs is possible, and amounts to component-wise assignment.

a = b;

Pointers and structs

A common case is a pointer to a structure:

particle ap = &a; ap->xvel; // frequently used, terse equivalent to (*ap).xvel.

It is common in in C to deal with complex data objects in a quasi-object-oriented fashion:

For example

// particle.h typedef struct particle_store { double mass; double xpos, ypos; double xvel, yvel; } particle_store, *particle; particle new_particle(double mass, double xpos, double ypos, double xvel, double yvel); void free_particle(particle p); void step_particle(particle p, double delta_t);

and

// particle.c #include <assert.h> #include <stdlib.h> #include "particle.h" particle new_particle(double mass, double xpos, double ypos, double xvel, double yvel) { particle result = (particle) malloc(sizeof(particle_store)); assert(result != NULL); // simple minded error handling: allocate or die! result->mass = mass; result->xpos = xpos; result->ypos = ypos; result->xvel = xvel; result->yvel = yvel; return result; } void free_particle(particle p) { free((void *) p); } void step_particle(particle p,double delta_t) { p->xpos += p->xvel * delta_t; p->ypos += p->yvel * delta_t; }

The parenthesized types are casts, which tell the C compiler to view an object of one type as if it had a different type. Casts are often used to swizzle pointer types at the boundary between system memory management functions (which can't know about your types) and user code, as they are here. C allows you to cast between pointer types with wild abandon. It assumes that you know what you're doing, and provides no support in case you don't.

Most modern languages free the programmer from the burden of dealing explicitly with memory reclaimation, but not C. C programmers need to think carefully about the lifetimes of the objects they allocate, and also about the ownership of objects that are accessed via a pointer (e.g., if you “free” a stack allocated object through a pointer, bad things will happen... very bad things). A natural consequence of this quasi-oriented style is that objects written in this style are almost never stack-allocated (i.e., the pointers may be local variables, but the structs they point to almost never are).

One of the great limitations of C is that the type system can deal only with fully grounded types, i.e., there are no parametric types as there are in Haskell (although you'll occasionally see the pre-processor bent in somewhat extreme ways to give rise to de facto parametric types via string substitution). Thus, if you need a particular data structure, e.g., a queue of particles, you have to hand roll a type-specific queue, with its constructors, allocators, etc. What follows is a lisp-ish queue implementation

// particle.h #include <stdbool.h> typedef struct particle_store { ... } *particle; ... typedef struct particle_cons_store { particle contents; struct particle_cons_store *next; } *particle_cons; particle_cons new_particle_cons(particle p, particle_cons next); void free_particle_cons(particle_cons pc); typedef struct particle_queue_store { particle_cons front; particle_cons rear; } *particle_queue; particle_queue new_particle_queue(); void free_particle_queue(particle_queue q); void particle_queue_insert(particle_queue q, particle p); bool particle_queue_isempty(particle_queue q); particle particle_queue_pop(particle_queue q);

and

// particle.c ... // particle particle new_particle(double mass, double xpos, double ypos, double xvel, double yvel) { ... } ... // particle_cons particle_cons new_particle_cons(particle p, particle_cons next) { particle_cons result = (particle_cons) malloc(sizeof(struct particle_cons_store)); assert(result != NULL); result->contents = p; result->next = next; return result; } void free_particle_cons(particle_cons pc) { free((void *) pc); // Note that we are not freeing the contents. } // particle_queue particle_queue new_particle_queue() { particle_queue result = (particle_queue) malloc(sizeof(struct particle_queue_store)); assert(result != NULL); result->front = NULL; result->rear = NULL; return result; } void free_particle_queue(particle_queue q) { // free any objects we contain... particle_cons current = q->front; while (current != NULL) { free_particle(current->contents); free_particle_cons(current); current = current->next; // yes, this is safe! } // free ourselves free((void *) q); } void particle_queue_insert(particle_queue q, particle p) { particle_cons new_cons = new_particle_cons(p,NULL); if (q->front == NULL) { // empty queue q->front = new_cons; q->rear = new_cons; } else { // non-empty queue q->rear->next = new_cons; q->rear = new_cons; } } bool particle_queue_isempty(particle_queue q) { return q->front != NULL; } particle particle_queue_pop(particle_queue q) { // first, make sure we're not empty... assert(q->front != NULL); particle result = q->front->contents; particle_cons old_front = q->front; q->front = q->front->next; free_particle_cons(old_front); return result; }

This code is somewhat complex (and untested!). The important thing to note that elements are added to the rear of the queue, and extracted from the front of the queue, and the very explicit handling of pointers, allocation and deallocation, etc.

Homework-11A Write a turtle graphic interpreter. Turtle graphics is a simple language for drawing, consisting of commands that are given to a turtle. Turtles have a position and heading. The commands are

moveto x y move to a particular point without drawing
forward n move forward n steps, drawing a line as you go
left a turn left (counter-clockwise) by a degrees
right a turn right (clockwise) by a degrees
push push the current graphic state
pop pop the current graphic state

You may assume that there is precisely one command per line, and that it has one of the forms above.

You need to emit instructions in postscript that do the drawing. A postscript file begins with the header:

%!PS

This should be followed by a blank line and then the command

1 setlinecap

Which rounds off the line segments we'll use in drawing, producing a more pleasing output.

You should assume that the turtle starts in position 0,0, pointed up (i.e., with a heading of 90 degrees, assuming the usual x-axis, counter-clockwise system). You should maintain a graphics state that consists of turtle location, and heading, and a stack of graphics state with the current state on top.

The PostScript language is a stack language, with parameters preceding commands. We are going to use a very small set of PostScript commands for this exercise.

To move to position x, y, issue the command

x y moveto

To draw a line from (x,y) to (x',y'), issue the commands

newpath x y moveto x' y' lineto stroke

After you've issued all of your drawing commands, issue

showpage

That's pretty much it. You assume that the coordinate system of the input space is the same as the coordinate system of the output space, and that there's no need for translation, rotation, or scaling. There are sample turtle graphics programs (zip). These programs were generated from Lindenmayer systems, which I may describe later.

The assignment is to write such an interpreter in C. You should use a struct for the graphic state, and dynamic storage allocation/deallocation (malloc,free) to manage the graphic state stack. You may find it helpful to develop a solution in Python first, and to use this to guide the development of your C program. You should turn in a printed copy of each generated image, along with your code.

Note: