[These notes are borrowed from Julian Bradfield, who created them as part of an old version of the Operating Systems course.] (Re-)Learning (rudimentary) C ----------------------------- There are many course notes available on the Web designed to introduce Java programmers to C. Most of the basic control constructs are very similar to Java (Java was designed that way). In understanding kernel source, there are some crucial features of C that do not occur in Java, and that have to be understood. The C pre-processor. -------------------- When a C program module (usually stored in a *.c file, e.g. sched.c) is compiled, there is a "pre-processing" phase before the actual compilation. This serves several purposes: it allows other physical files to be included in the current file before it is compiled. Such files are usually "header files" (with a .h suffix), which contain declarations of external functions and variables that this program will use. Thus, a header file performs something of the function of a Java interface, although it is cruder. For example, almost all C user programs (but not kernel source!) contain the line #include which includes the declarations for the functions in the standard C runtime library. It allows so-called "macros" to be defined. For example, if your program contains the line #define FOOBAR "derived from a vulgar acronym" then before the program is compiled, any occurrence of the word FOOBAR (except inside strings and comments) will be replaced by "derived from a vulgar acronym". Macros are sometimes used to define constants that are considered somehow fundamental; or constants that may vary from one architecture to another! The latter use relies on the third point: It allows "conditional compilation": that is, the compiler can be given different code, depending on the setting of macro-defined constants. For example, if your program contains the lines: #ifdef LINUX ... some code for linux #else ... some code for something else #endif then the compiler will see the linux code only if earlier in the file the macro symbol LINUX is defined. Structs and pointers -------------------- C does not have objects. The way to build complex data structures in C is via structs and pointers. A struct is a bit like an object: it is a bunch of data gathered together into a single record. The data can be normal data, like Java instance variables, or functions. Example: the declaration struct some_stuff_struct { int a; int b; char s[24]; }; declares a type called "struct some_stuff_struct", which contains two integers and a 24-character string (in C, a string and an array of characters are (almost) the same -- not like Java!). The components can be accessed using dot notation, rather as in Java: struct some_stuff_struct mystuff; // declare variable mystuff to have // type struct some_stuff_struct mystuff.a = 42; // set "a" component to 42 mystuff.s[10] = 'T' ; // set character 10 of the string // to 'T' It gets boring typing "struct" all the time, so it is common to define a short name like this: typedef struct some_stuff_struct some_stuff; This makes "some_stuff" mean the same as "struct some_stuff_struct". In fact, this is often done at the time of the original declaration of the struct type, so you will see, instead of the first declaration above: typedef struct some_stuff_struct { int a; int b; char s[24]; } some_stuff; // simultaneously declare the struct and its short name Pointers are like object references in Java, except that a C pointer can refer to anything, not just a struct. Pointers are often used with structs to achieve the same sort of effect as Java objects. If you have a piece of data, you can get a pointer to it by prefixing an ampersand &. If you have a pointer, you can get the thing it points to by prefixing a star *. Owing to the highly confusing way in which C types are formed, in order to declare a pointer variable, you have to use a star... Here is an example: some_stuff mystuff; // declare mystuff as above some_stuff *stuffptr; // stuffptr is a variable whose type is // "pointer to some_stuff". mystuff.a = 42; // set "a" component of mystuff to 42 stuffptr = &mystuff; // stuffptr now points to mystuff if ( (*stuffptr).a == 42 ) { /* this is true */ } else { /* this is false */ } Now we can pass stuffptr around in function calls, and the functions will get a pointer to mystuff -- so they can change it. Because one is usually passing around pointers to structs, and then using them with things like (*stuffptr).a, C provides a short form for that: we can write stuffptr->a (mnemonic: the a field of the thing that stuffptr points to). So really, stuffptr->a is the equivalent of the Java notation obj.a , and the C notation mystuff.a doesn't have an equivalent in Java, because in Java you can only ever get a reference to an object, not the object itself. The other use of pointers is to allow a function to modify an argument passed to it. Of course, that can never really happen, so what one does is pass a pointer to the argument; then the function deferences the pointer, and gets the original argument. For example, consider the following code fragment: void inc(int x) { x++; } int a = 42; // integer inc(a); // call the inc routine, passing a /* a still has the value 42 */ This works exactly as in Java, and so the value of a is unchanged by passing it to the function inc, which just increments its parameter variable x. However, in C we can write: void incp(int *xptr) { (*xptr)++; // increment the integer pointed to by xptr } int a = 42; incp(&a); // call incp, passing it a pointer to a. /* a now has the value 43 */ This is rather like defining a class containing a single integer field, but without the trouble of actually doing so. Putting it together, here is how one defines a simple linked list of integers in C: // the following makes list_pointer mean "pointer to struct list_record" typedef struct list_record *list_pointer; // pre-declare the pointer type // the following actually declares the struct itself struct list_record { int value; // the integer in this element of the list list_pointer next; // pointer to the next element in the list }; In C, the null pointer is just (the integer) 0. However, there is often a predefined macro constant NULL, so that you write NULL whenever you mean a null pointer. So far, I haven't talked about you actually create a new structure (the equivalent of the Java new operation). This is, unfortunately, messy. With luck, you won't have to do it.