Yesterday I learned four things.
1. How to use a void pointer in c (void * my_ptr). How to access its value (it can't be directly dereferenced so you have to cast it to another pointer like and dereference
that: *(int *) my_ptr
2. How to write functions that take a variable number of arguments. You declare the function by first listing the fixed parameters, then an ellipsis at the end to indicate zero or more additional arguments are to be passed to the function. Since C does not provide a way to access the number of variable arguments passed directly, you must generally have a way of knowing this, either directly (by having one of your fixed arguments be the number of arguments) or indirectly (such as in printf,)
To access the variable arguments in your function definition, you must use the macros defined in the header file stdarg.h. The first thing is to declare a pointer variable of type va_list generally called arg_ptr. Then you call va_start() passing it arg_ptr and the name of the last fixed argument. To retrieve each variable argument, call va_arg() passing it the pointer arg_ptr and the data type of the next argument. The return value of va_arg is the value of the next argument. Then call va_end(arg_ptr).
The next thing I learned was how to write a simple makefile. In its very simplest form a makefile is just one rule of the form:
target: prereq1, prereq2
:command
So a very simple example:
hello: hello.c
:gcc hello.c -o hello
It gets more complicated because of the concept of dependencies. The make utility looks at the last modified date of the various files and recompiles a target if its last modified date is earlier than that of any of its prerequisites.
Finally, I learned a bit about posix threads. I learned about an interesting race condition that can occur if condition variables are not used carefully. Condition variables are used to wait and signal but if a thread is not waiting on a condition variable before another thread signals, that signal will be lost and a race condition occurs if the thread that is supposed to wait for the signal is delayed in its execution. In such a case, if the first thread signals and then waits for action to be taken by the waiting thread and the waiting thread begins its waiting state slightly too late, a deadlock is likely to occur. One really common and clever solution to this problem is to create a global variable that the signalling thread increments before it signals. So when the waiting thread begins execution, before putting itself in the waiting state, it first checks the value of the global variable. Finally, a mutex is required so that the signalling thread locks the mutex, increases the global variable, then signals, then releases the mutex. Similarly, the waiting thread locks the mutex, checks the global variable, calls wait and unlocks the mutex. wait takes the mutex as one of its arguments so that it can unlock the mutex before putting the thread on the wait queue.