Discussion Week 1: Why I love (or hate) C
Haryadi S. Gunawi, Remzi H. Arpaci-Dusseau

Below are the C related stuffs we covered in week 1. If you're a C expert, you can just simply skip to the part where you find interesting. If you are a C beginner, you could read the C book in addition to this quick guide.

Not all the code in this page were tested. Please email me (login: haryadi) if you find one or more bugs.

Part 1:

Part 2:

Part 3:

Compiling

For each source code below, you simply copy paste the source code to a .c file (e.g. test.c), compile it, and run the executable. If some include files are missing, it's your responsibility to add them.

prompt> gcc test.c
prompt> ./a.out
For more on compling, please read this tutorial

Hello World!

You can make a program that prints "Hello World!" in Java or other languages. How about in C? It's as simple as this:
int main ()
{
    printf("Hello World!\n");
    return 0;
}

Check the manual

If you compile the hello world program above, you will get an error warning: warning: incompatible implicit declaration of built-in function 'printf'. The reason is you must use printf appropriately, that is, before making a library call, you need to check the manual first:

prompt>  man 3 printf

Note the number 3 between the man and printf. The number is needed in this case because there are many programs that use printf, e.g. shell, awk, C, etc. man printf brings you the first manual it can find for printf. In this case, it will give you man 1 printf which is the user command for printf.

Thus, the rule of the thumb is: if you couldn't find what you're looking for by just typing man [your-func], you should use the numberings, man 1 [your-func], man 2 [your-func], and so on until you find the one you're looking for.

The manual tells you that if you want to use printf properly and correctly, you should add #include <stdio.h>. So, here is your final code:

#include <stdio.h>
int main ()
{
    printf("Hello World!\n");
    return 0;
}
Thus, the lesson is always check the manual. Specifically, you should look for:
  1. What are the include files?
  2. The description of the function -- of course!
  3. Return value, including the error codes
  4. The extra libraries that you need to link your program with. For example, if you type man cos to compute a cosine function, the manual tells you that you need to link your program with -lm, i.e. the math library.

Codes for printf

Below are some of the different ways you can print your integer, floating number, string, etc. Try it, and see by yourself the differences. See C book, page 244 for more.
#include<stdio.h>
int main()
{
    int x = 78;
    double y = 12.34;
    char aWord [20] = "I like C";

    printf("a: |%d|\n", x);
    printf("b: |%1d|\n", x);
    printf("c: |%2d|\n", x);
    printf("d: |%3d|\n", x);
    printf("d: |%4d|\n", x);
    printf("e: |%-3d|\n", x);
    printf("f: |%-4d|\n", x);
    printf("g: |%02d|\n", x);
    printf("h: |%03d|\n", x);

    printf("i: |%f|\n", y);
    printf("j: |%5.2f|\n", y);
    printf("k: |%8.2f|\n", y);
    printf("l: |%08.2f|\n", y);
    printf("m: |%-8.2f|\n", y);
    printf("n: |%5.4f|\n", y);

    printf("o: |%s|\n", aWord);
    printf("p: |%30s|\n", aWord);
    printf("q: |%-30s|\n", aWord);
}

Declaring Variables

Declaring a variable is straighforward. First you define the type (e.g. int, double), and the name of your variable. Optionally you can initialize them.

int x;
double y;
int z = 7;
Note that C compilers require you to declare all your variables in the beginning of the function. If you want to declare variables in the middle of the function, such as:
...
for (int i = 0; i < 10; i++)
...
You need to compile your program with c99 standard: gcc -std=c99.

Strings

In C, there is no built-in string type, and that's why you love C ... or not. A string is basically an array of characters. Thus, to declare a string, you need to build an array of characters (the same way you build an array of integers).

char aString [10];

If you want to initialize it with empty string, you can do it in different ways: during declaration, or use strycpy (again, don't forget to check man strcpy), or set the first character to NULL.

1) char aString [10] = "";
2) strcpy(aString, "");
3) aString[0] = '\0';

If you don't initialize the string during the declaration, you cannot simply assign the string using the equal "=" operator. You use strcpy.

aString = "What? I can't do this?!!#@*%";
strcpy(aString, "this way is correct");
Finally, if you have two strings (two arrays of characters), and you want to copy one from another, you cannot use the equal "=" operator. You must use strcpy.
char dst [30] = "empty";
char src [30] = "copy me please";
dst = src; // you can't do this, sorry
strcpy(dst, src); // this is right

Structure (No Class)

In C++ and Java, you learned how to create an object using class. In C, there's no such thing as class, you only get struct, which is basically similar to class but you cannot define a member public or private, neither can you write member functions inside a struct. All variables in a struct are public. Remember to put a semicolon ";" after you define a structure.
struct container {
    int x;
    int y;
}; // remember to put a semicolon here
int main ()
{
    struct container c;
    c.x = 0;
    c.y = 2;
    printf("%d \n", c.x);
    printf("%d \n", c.y);
    return 0;
}

Control

In C, there are two control flows: loop and branch. There are four forms of loops:
// Form 1: while
i = 0;
while (i < 100) {
    printf("%d");
    i++;
}
// Form 2: do while, executes the while-body at least once 
// before performing the check
i = 0;
do {
    printf("%d");
    i++;
} while (i < 100);
// Form 3: for
for (i = 0; i < 100; i++) {
    printf("%d");
    i++;
}
// Form 4: goto, people rarely do this,
//         but you see this a lot in the Linux kernel
  i = 0;
repeat:
  printf("%d");
  i++;
  if (i < 100) {
      goto repeat;
  }
There are two forms of branch: if and switch. if can accept complex logic, switch can only accept equality. For more please see the C book.
if (i < 10) {
    // do something
} 
else {
    // do something else
}
switch(i) {
  case 1: 
      printf(" i is 1 \n"); 
      break; // without break you fall through to the second case
             // even though i is 1 .. try it
  case 2:
      printf(" i is 2 \n");
      break;
  default:
      printf(" i is neither 1 nor 2 \n");
}

Constants

Sometimes you want to have a constant that you use everywhere in your code. To do that you can use #define:
#define LINE_MAX 80
int main() {
    int i = 0;
    while (i < LINE_MAX) {
        fgets(...);
    }
}

I/O: Getting a character

To get a character one by one from a file or the prompt, you can use the getchar function (again, don't forget to check man getchar)).
char a;
while (1) {
    a = getchar();
    if (a == EOF) 
        break;
    printf("%c", a);
}
Or you could be a little bit fancy by doing this:
while ((a = getchar()) != EOF) {
    printf("%c", a);
}
(a = getchar()) != EOF basically performs two things: first it stores the return value of getchar to a, and then compares a with EOF. To try the program above, compile and run this way:
> gcc test.c
> ./a.out   ---> this will simply wait for your character inputs
> ./a.out < aFile --> this will take and print each character in the aFile

I/O: Reading a line

To read a line from the prompt or a file, you can use gets or fgets. Let's use gets first.
char input [10];
gets(input);
printf("%s", input);

Compile and run that. Moreover, when you type a line, use more than 10 characters. You suppose to get Segmentation fault because the OS only allocates 10 characters for input, but you overflow it; your program attempts to write to invalid memory addresses.

A better way is to use fgets, which requires you to specify how many characters you want to read from a file or from the prompt. If you wrongly specify the length, it's your fault.

char input [10];
fgets(input, 10, stream);
printf("%s", input);

Run it, and type as many characters as you want, and see what happens. Supposedly, it only accepts 9 characters (not 10? how do I know? read the manual :), the 10th character is for the null-terminating character.

If you feed in 15 characters, and it only accepts 9 characters, where would the other 6 characters go? Does the C library discard or buffer the rest of the characters? The answer is buffer. Try calling fgets twice and see what will happen.

fgets(input, 10, stream);
printf("%s", input);
fgets(input, 10, stream);
printf("%s", input);

Functions

So far we do everything inside the main function. Suppose, we want to do stuffs in another function, you can simply create one similar to the main function. Try compile the code below:
#include<stdio.h>
int main()
{
    f();
}
void f()
{
    printf(" Hello world !\n");
}
You should get a warning:
test.c:7: warning: conflicting types for 'f'
test.c:4: warning: previous implicit declaration of 'f' was here
The warning is produced by the C compiler because the compiler works from the top to the bottom of the file. In short, when the compiler sees that the main function calls a function f, it hasn't seen the declaration of f. You can get this around by putting the whole function f(){..} before main(){..}, but this is not the best solution. What you need to do is to tell in advance the functions that you want to call. This is also called as function prototype. In this case we need to copy the function header and put it before main.
#include<stdio.h>
void f(); // --> this is called a function prototype;
int main()
{
    f();
}
void f()
{
    printf(" Hello world !\n");
}

Call by value (or pass by value)

When calling a function with some arguments, different languages process the argument in different ways: call by value (pass by value) or call by reference (pass by reference). In C, sadly, we only have call by value. Let's see some examples to understand call by value vs. call by reference.

Let's see how call by reference works. The problem this works only in C++, not C:

void makeDouble (int & x)
{
    x = x * 2;
}

int main ()
{
    int y = 7;
    makeDouble (y);
    printf("%d \n", y);
    return 0;
}
Copy paste the above program into test.cpp (Notice the .cpp extension!). Compile the program using g++, the C++ compiler:
> g++ test.cpp
> ./a.out
The result is 14. Why? Notice the & (ampersand) sign in makeDouble(int & x). The ampersand sign informs the C++ compiler that when makeDouble is called, it should pass the address of y (rather than the value) to the x in the makeDouble function. Simply stated, x is basically y in this example. How do I prove this? I can't, sorry. The reason is int & x is a special variable that we can't debug at this level. Trust me for a while.

Now, suppose in C, we want to do the same thing. Unfortunately, C cannot accept the int & x syntax. So, let's remove the &:

void makeDouble (int x)
{
    x = x * 2;
}
int main ()
{
    int y = 7;
    makeDouble (y);
    printf("%d \n", y);
    return 0;
}
Copy paste the program above into test.c (Notice the .c extension!). Compile the program using gcc, the C compiler:
> gcc test.c
> ./a.out 
If you know how C works, the above code will print 7, not 14! The technical explanation is when makeDouble is called , only the value of y is passed to x. Hence, the makeDouble function doubles x in a different memory location. Specifically, y lives in the main stack and x lives in the makeDouble stack. Don't trust me? Let's debug their addresses.
void makeDouble (int x)
{
    printf("Time 2: Address of x = %08x, Value of x = %d \n", &x, x);
    x = x * 2;
    printf("Time 3: Address of x = %08x, Value of x = %d \n", &x, x);
}
int main ()
{
    int y = 7;
    printf("Time 1: Address of y = %08x, Value of y = %d \n", &y, y);
    makeDouble (y);
    printf("Time 4: Address of y = %08x, Value of y = %d \n", &y, y);
    return 0;
}
If I compile and run the program above in my machine, I get this output:
Time 1: Address of y = bf94b7e0, Value of y = 7 
Time 2: Address of x = bf94b7c0, Value of y = 7 
Time 3: Address of x = bf94b7c0, Value of y = 14 
Time 4: Address of y = bf94b7e0, Value of y = 7 
You can see that the addresses of x and y are different! That's the core reason why updating x does not affect y. Surely this looks simple (No?). But when we get to pointer and structure initializations, printing the addresses will help you understand what's going on at the low level.

As a side note, the addresses above are virtual address, not physical memory address. The memory management coverts virtual address to physical address. This will be much clearer when we have discussed memory management in the class.

Pointers and Addresses

So, using the previous example, can we update y in the makeDouble function? The answer is yes, by using pointer. In C, we can create pointers to integer, char, etc. Let's warm up a little bit. For more, please read the C book, Chapter 5.
int main()
{
    int I = 7;
    int *pI = & I;    // make pI points to I
    int **ppI = & pI; // make pI points to ppI

    printf("    I is %d \n", I);
    printf("  *pI is %d \n", *pI);
    printf("**ppI is %d \n", **ppI);
}
The logical view looks like this: ppI --> pI --> I (7). Now, let's print the addresses to prove this:
int main()
{
    int I = 7;
    int *pI = & I;    // make pI points to I
    int **ppI = & pI; // make ppI points to pI
    
    printf("  I's address is %08x ,   I's value is %d   \n", &I   , I  );
    printf(" pI's address is %08x ,  pI's value is %08x \n", &pI  , pI );
    printf("ppI's address is %08x , ppI's value is %08x \n", &ppI , ppI);
}
The output in my machine is:
  I's address is bfbec280 ,   I's value is 7 
 pI's address is bfbec27c ,  pI's value is bfbec280 
ppI's address is bfbec278 , ppI's value is bfbec27c 
Let's discuss the addreses first. These addresses point to the area within the stack that keeps the I, pI, and ppI variables. As you can see, their addresses differ by 4 (i.e. bfbec278 + 4 = bfbec27c , bfbec27c + 4 = bfbec280). This is because an integer and a pointer are only 4 bytes long. (Note in 64-bit system, a pointer is 8 bytes long).

At the memory-level, a pointer basically contains the address of what it is pointing to. For example, since pI points to I, pI value is the address of I (i.e. bfbec280). Since ppI points to pI, ppI value is the address of pI (i.e. bfbec27c). In summary, this what the stack looks like:

           Physical View    | Logical View
      ---------------------------------------
      | Address  | Value    | 
      |          |          |
      |   ..     |  ..      |
      |          |          |
      | bfbec278 | bfbec27c | ppI ---------
      |          |          |             |
      | bfbec27c | bfbec280 | pI ----  <---
      |          |          |       |
main: | bfbec280 |   7      | I <----

As a side note, in C++ you don't have to deal with pointers because C++ developers let you go around with that by using call by reference. In Java, the developers are much nicer -- everything is a pointer, but they hide it for you.

Messing up a constant (just for fun)

So, your teachers once told you that you cannot modify a constant. Well, they lied (but please don't tell them I tell you this). Here's the 1 million dollar question, can you change the constant below?
const int x = 100;
printf("x is %d , can you change me? \n", x);
The answer is yes, and here's the trick below. The idea, const is only bound to variable x, but not the pointer messer.
const int x = 100;
int * messer = & x;
*messer = 1000000;
printf("x is now %d , so amazing ..\n", x);

Initializing a struct in another function (back to serious mode)

Okay, you pay an expensive tuition, so it's better to go back to serious mode. Below, a structure is creted in the main function but initialized in the other function. Will the program output garbage numbers or the initial numbers?
struct something {
    int x;
    int y;
};
void init(struct something s1)
{
    s1.x = 100;
    s1.y = 200;
}
int main()
{
    struct something s0;
    init(s0);
    printf(" %d %d \n", s0.x, s0.y);
}
Exactly! The program will print garbage because in C everything is passed by value. When init is called, the OS allocates a memory of 8 bytes (ask yourself why 8?) in the stack for s1. The contents of s0 is copied to s1. Thus, s1 and s0 are at different addresses. When we initialize x and y to 100 and 200, the changes are reflected only to s1. After the initialization is done, the memory space from the invocation of init will be wiped out. Nothing affects s0.

Stack vs. Heap

So far, you have observed how stack comes into play. Specifically, you have seen the stack addresses (e.g. bf94...). There's another part of the virtual memory of a program that we call as the heap. Global variables are stored in the heap. Let's check it out:
int g = 30;
void f()
{
    int y = 20;
    printf(" y's address is %08x , y's value is %d \n", & y, y );
    printf(" g's address is %08x , g's value is %d \n", & g, g );

}
int main()
{
    int x = 10;
    f();
    printf(" x's address is %08x , x's value is %d \n", & x, x );
    printf(" g's address is %08x , g's value is %d \n", & g, g );
}
The output on my machine:
 y's address is bfd1e374 , y's value is 20 
 g's address is 08049684 , g's value is 30 
 x's address is bfd1e3a0 , x's value is 10 
 g's address is 08049684 , g's value is 30 
As you can see x and y live in the stack (i.e. addresses bfd1...), while g lives in the heap (address 0804...). Stack grows from lower address to upper address, and heap is the other way around.

Remember that the memory space used in the heap will be wiped out when the program exits, while the memory space used in the stack will be wiped out when the corresponding function exits.

malloc vs. new

In C++, you can initialize a pointer by using the new construct (e.g. int *c = new int). In C, there is no new construct. When you want to allocate a memory dynamically, you must use malloc (and .. don't forget to check man malloc).
struct something
{
    int x;
    int y;
};
int main()
{
    int *a;
    struct something *s;

    a = malloc(sizeof(int));
    s = malloc(sizeof(struct something));

    *a = 100;
    (*s).x = 200;
    s->y = 300;

    printf(" %d %d %d \n", *a, (*s).x, s->y);
    printf(" Address of a = %08x \n", a);
    printf(" Address of s = %08x \n", s);

    free(a);
    free(s);
}
There is a couple of notes here.

First, malloc expects you to give the size of the requested memory. Why? isn't it obvious that an integer is 4 bytes and struct something is 8 bytes? In this example, yes. But malloc is a generic function; it can be used for allocating a dynamic-length array (e.g. int *array = malloc( 10 * sizeof(int) );). We'll see an example of dynamic-length array later.

Second is about dereferencing a pointer. For example, since a is a pointer to an integer, to get the number, we need to dereference the pointer once (i.e. *a). The same thing for structure; since s is a pointer to a structure, dereferencing it (*s) will give us the structure. Thus, to access the fields inside the structure we use this notation (*s).x. Yuch! This is a very ugly notation. Luckily, C helps us a little by allowing us to use a more compact notation s->x.

Third, malloc will allocate memory space dynamically, which means that the allocated memory resides in the heap (check the address again if you don't believe me). Again, the allocated memory will not be wiped out until the program exits. A good practice is to always free the allocated memory once you are done with it. Otherwise, you have memory leak problem.

Error Handling

Always remember in your life that resources are limited and failures/errors happen all the time. This means that for each library calls or system calls that you make, you must check the returned error code. We have observed that OS developers often ignore in-kernel error-codes such that you can't trust your OS to work reliably. Thus, you should be a living example for them.

Using malloc is an example where we can run out of memory. Run this program for a while, and see what will happen:

struct something
{
    int x;
    char y [1024*32];
};
int main()
{
    struct something *s;
    while (1) {
        s = malloc(sizeof(struct something));
        printf(" Address of s = %08x \n", s);
        s->x = 1;
    }
}
You'll get a segmentation fault. What's happening is you allocate memory all the time without freeing it. Then, when you run out of memory, you forget to check the returned error code from malloc. Reading man malloc tells you that malloc could return NULL if there is no memory left. So, this is the correct code:
while (1) {
    s = malloc(sizeof(struct something));
    // error handling
    if (s == NULL) {
        printf(" I hate malloc ... \n");
        printf(" I hate memory ... \n");
        printf(" I hate NULL   ... \n");
        printf(" I hate C      ... \n");
        printf(" I hate 537    ... \n");
        printf(" I hate OS     ... \n");
        printf(" I love myself though ... \n");
        exit(1);
    }
    printf(" Address of s = %08x \n", s);
    s->x = 1;
}

Initializing a pointer in a wrong way (Important)

In the previous example, you want to initialize a pointer using malloc, but your "unique" error handling could be very long (especially if you're grumpy). Thus, you might want to initialize the pointer in a different function (i.e. a wrapper) that deals with the error handling stuffs such that your main code looks neat. But you could be careless. Look at the code below. It looks perfectly like what you want. Compile and run it.
struct something
{
    int x;
    int y;
};
void init_structure(struct something *s1)
{
    s1 = malloc (sizeof(struct something));
    if (s1 == NULL) {
        // your error handling (or complaints) goes here
        exit(1);
    }
}
int main()
{
    struct something *s0;
    init_structure(s0);
    s0->x = 10;
    s0->y = 20;
}
You'll get a segmentation fault! Why? If you're adventurous (or you only take 1 class), think about this for a while, and try to think of the solution. If you take five 500-level courses, I feel you, so you can proceed.

Let's debug the program by showing the addresses and the values again. Remember that the value of a pointer is the address of what it is pointing to. Another way to think about this is, in memory, an integer holds an integer number, while a pointer holds an address.

void init_structure(struct something *s1)
{
    printf("Time 2:  s1's address is %08x, s1's value is %08x \n", & s1, s1);
    s1 = malloc (sizeof(struct something));
    printf("Time 3:  s1's address is %08x, s1's value is %08x \n", & s1, s1);
}
int main()
{
    struct something *s0;

    printf("Time 1:  s0's address is %08x, s0's value is %08x \n", & s0, s0);
    init_structure(s0);
    printf("Time 4:  s0's address is %08x, s0's value is %08x \n", & s0, s0);
    s0->x = 10;
    s0->y = 20;
}
My output is:
Time 1:  s0's address is bf9c19e0, s0's value is 002f85d0
Time 2:  s1's address is bf9c19c0, s1's value is 002f85d0
Time 3:  s1's address is bf9c19c0, s1's value is 09a56008
Time 4:  s0's address is bf9c19e0, s0's value is 002f85d0
First, note the difference between the addresses of s0 and s1. They are different because s0 lives in the main stack, while s1 lives in the init_structure stack. This implies that any modification to s1 in init_structure only happens to s1 but not to s0!

At Time 1, s0 points to a garbage (002f85d0), thus at Time 2, s1 initially gets a garbage too. Remember again, in C everything is passed by value. In this case, the value of s0 (which is a garbage) is copied to s1.

At time 3, s1 gets a memory at the heap from the malloc call. This heap area is only pointed by s1 (not s0). After the init_structure exits, s1 is destroyed.

At time 3 this is what the memory looks like:

           Physical View    | Logical View
      ---------------------------------------
      | Address  | Value    |
      |          |          |
HEAP: | 09a56008 | something| (heap) <--
      |          |          |          |
      |          |          |          |
      |   ..     |  ..      |          |
      |          |          |          |
      |          |          |          |
      |          |          |          |
inits:| bf9c19c0 | 09a56008 | s1 -------
      |          |          | 
main: | bf9c19e0 | 002f85d0 | s0 ---[points to]---> garbage
And at time 4, the invocation of init_structure is wiped out, s1 is gone. The result is: first, s0 still points to a garbage, and we have a memory leak. The dynamically-created memory is still allocated but not pointed by anyone, hence, cannot be referenced.
           Physical View    | Logical View
      ---------------------------------------
      | Address  | Value    |
      |          |          |
HEAP: | 09a56008 | something| (heap) <---- X    (memory leak!)
      |          |          |            
      |          |          |           
      |   ..     |  ..      |           
      |          |          |           
      |          |          |           
      |          |          |           
inits:| bf9c19c0 | 09a56008 | s1
      |          |          | 
main: | bf9c19e0 | 002f85d0 | s0 ---[points to]---> garbage

Initializing a pointer in a right way (Important)

Here's a right way to initialize a pointer in a different function. In previous courses, maybe you have understood the high-level picture of pointers. However since you are to be a system programmer in this course, I encourage you to know what's going on underneath in the memory. That's why this is a long tutorial.
void init_structure(struct something **s1)
{
    *s1 = malloc (sizeof(struct something));
}
int main()
{
    struct something *s0;
    init_structure(& s0);
    s0->x = 10;
    s0->y = 20;
    printf(" %d %d \n", s0->x, s0->y);
}
Let's print the addresses and values again:
void init_structure(struct something **s1)
{
    printf("Time 2:  s1's address is %08x,  s1's value is %08x \n", & s1, s1);
    printf("        *s1's address is %08x, *s1's value is %08x \n",   s1, *s1);
    *s1 = malloc (sizeof(struct something));
    printf("Time 3:  s1's address is %08x,  s1's value is %08x \n", & s1, s1);
    printf("        *s1's address is %08x, *s1's value is %08x \n",   s1, *s1);
}
int main()
{
    struct something *s0;
    printf("Time 1:  s0's address is %08x,  s0's value is %08x \n", & s0, s0);
    init_structure(& s0);
    printf("Time 4:  s0's address is %08x,  s0's value is %08x \n", & s0, s0);
    s0->x = 10;
    s0->y = 20;
    printf(" %d %d \n", s0->x, s0->y);
}
My output is:
Time 1:  s0's address is bfb14410,  s0's value is 002f85d0 

Time 2:  s1's address is bfb143f0,  s1's value is bfb14410 
        *s1's address is bfb14410, *s1's value is 002f85d0 

Time 3:  s1's address is bfb143f0,  s1's value is bfb14410 
        *s1's address is bfb14410, *s1's value is 09192008 

Time 4:  s0's address is bfb14410,  s0's value is 09192008 
The virtual memory at time 3 looks like this:
           Physical View    | Logical View
      ---------------------------------------
      | Address  | Value    |
      |          |          |
HEAP: | 09192008 | something| (heap) <----
      |          |          |            |
      |          |          |            |
      |   ..     |  ..      |            |
      |          |          |            |
      |          |          |            |
      |          |          |            |
inits:| bfb143f0 | bfb14410 | s1 ----    |
      |          |          |       |    |   (note: *s1 is basically s0)
main: | bfb14410 | 09192008 | s0 <---    |
                                 ---------
The virtual memory at time 4 looks like this:
           Physical View    | Logical View
      ---------------------------------------
      | Address  | Value    |
      |          |          |
HEAP: | 09192008 | something| (heap) <----
      |          |          |            |
      |          |          |            |
      |   ..     |  ..      |            |
      |          |          |            |
      |          |          |            |
      |          |          |            |
inits:| bfb143f0 | bfb14410 | s1 ----    |
      |          |          |            |   (note: *s1 is basically s0)
main: | bfb14410 | 09192008 | s0 ---------
Thus, at time 4, your struct pointer has been initialized correctly. What about time 1 and 2? Make yourself the memory snapshots for time 1, 2, and 4.

Array, and more crazy pointers

Some notes about array in C. First, C does not let you declare an arbitrary length array (e.g. C does not allow you to do this: int array [] = new int [var]). You can only declare a fixed-size array.
int anArray [10];
Second, how about if you need to make an arbitrary-size array? You use malloc. An array is technically a pointer to some elements stored consecutively in memory. For example, this array initialization is the same as above, except you need to free the memory after you finish using it.
int *anArray;
anArray = malloc ( 10 * sizeof(int) );
...
free(anArray);
Third, you normally access the elements in the array using indices, such as anArray[i]. For example:
int main()
{
    int anArray[10];
    int i;
    for (i = 0; i < 10; i++)
        anArray[i] = i;
    for (i = 0; i < 10; i++) 
        printf(" %d \n", anArray[i]);
}
You might like more challenging stuffs, so you ask yourself, can I iterate the array without using indices? Sure you can. anArray is simply a pointer to a consecutive integers in the memory. Thus, we could create another pointer to integer that we could fully control to access the elements of the array:
int main()
{
    int anArray[10];
    int *fancy;
    int i;

    for (i = 0; i < 10; i++)
        anArray[i] = i;

    fancy = anArray;
    for (i = 0; i < 10; i++) {
        printf(" fancy: %d \n", *fancy);
        fancy++;
    }
}
The code above uses an extra integer-pointer, fancy, that is incremented by one in every iteration. The C compiler knows that fancy is an integer (4 bytes long), therefore when we increment fancy by one, the C compiler increments the address that fancy is pointing to by 4. To understand this more, try these two examples below:
 int main() { int anArray[10]; int *fancy; int i;

    for (i = 0; i < 10; i++)
        anArray[i] = i;

    fancy = anArray;
    for (i = 0; i < 10; i++) {
        printf(" i=%d --> %d \n", i, *fancy);
        fancy = fancy + 2;
    }
}
The code above should print 0, 2, 4, 6, 8, and then some garbage. You should know the reason by now: the last five accesses are out-of-bound. What?? C lets me do this? Yes, C expects you to be a responsible programmer. Unlike Java, which will be warn you if there's an out-of-bound array access, C treats you like an adult (which could be a good or a bad thing).

Here's the last example on array. Compile and run this:

int main()
{
    long long anArray[10];
    int *fancy;
    int i;

    for (i = 0; i < 10; i++)
        anArray[i] = i;

    fancy = anArray;
    for (i = 0; i < 10; i++) {
        printf(" i=%d --> %d \n", i, *fancy);
        fancy++;
    }
}

The output should be 0, 0, 1, 0, 2, 0, 3, 0, 4, 0. How come? Here's the hint: long long creates 64 bits (8 bytes) number, while int only represents 32 bits (4 bytes) number. You should be able to figure out the rest.

Strcpy

Now you have enough understanding about pointers. You should be able to quickly predict what the output of this program:
int main()
{
    char *a;
    char b [10];
    sprintf(b, "hello");
    strcpy (a, b);
}
You're gonna say the program will crash because a has not been given memory. You're right, but don't forget to check man strcpy. The manual says that the library will not allocate memory for a, thus the program will crash.

Command-line arguments

So far, we simply create a program that does exactly one thing. But sometimes, we want to have the program do different things without recompiling the program. We can do that by feeding the program with more command-line arguments. Try the code below and play around with it.

int main(int argc, char **argv)
{
    int i = 0;
    char * anArg;
    int option;

    if (argc != 4) {
        printf(" ./a.out [1 or 2] [a word] [a word] \n");
        exit(1);
    }

    // this prints all the args
    for(i = 0; i< argc; i++) {
        anArg = argv[i];
        printf("  arg %d = %s \n", i, anArg);
    }
    printf("\n");

    // do something, depends on the arg
    printf(" I want to print : ");
    option = atoi(argv[1]);  // change a string to an int, see 'man atoi'
    if (option == 1) {
        printf(" %s \n", argv[2]);
    }
    else if (option == 2) {
        printf(" %s \n", argv[3]);
    }
    else {
        printf(" I only accecpt 1 or 2 \n");
    }
}
Compile and run it:
> ./a.out 1 abc def
> ./a.out 2 abc def

Makefile

Please read "Separate Compilation" and "Makefiles" from this tutorial

Why C?????

Congrats! We're done with all the examples. At this point, you should be familiar with what happening at the low level of the system. But you might still wonder why we're doing all these stuffs.

First, almost all operating systems are written in C because C lets you do low-level tricks, sometimes to make things go fast.

Second, you might wondering why I give these crazy examples as if we have lots of spare time. Well, the reason is, you'll be a system programmer. OS developers tend to use one of the tricks above to do their work. For example, they love to use extra pointers to access some relevant parts of a given array. A more specific example: A network packet is comprised of lots of headers (IP, TCP/UDP, MAC Address, etc.). When the OS receives a network packet, it only sees an array of bytes. The OS programmers need to manage extra pointers to access those specific headers.

So, hopefully this tutorial will bring benefits to your future.