Resource | Progress | Division | Year |
---|---|---|---|
Learn C The Hard Way | 6/55 | Exercises | 2011 |
Wikibooks: C Programming | 5/33 | Sections | |
C Development on Linux | 3.3/7 | Sections | 2012 |
The C Programming Language | 1.5/8 | Chapters | 1990 |
Cprogramming.com Tutorial | -/16 | Sections | |
The C Puzzle Book | -/29 | Problems | 1998 |
Harvard Extension School Unix Systems Programming | 0/14 | Class | 2012 |
There are countless online resources to learn the C programming langauge. Learn C The Hard Way is an incomplete web text by Zed Shaw that attempts to teach “good modern” C programming practices, which follows his popular text, Learn Python The Hard Way. Wikibooks: C Programming is a featured book consisting of thousands of edits made by members of the Wikibooks community. C Development on Linux is a relatively short series of articles introducing C development on Linux/Unix systems. It requires a basic understanding of programming. Cprogramming.com Tutorial is another online resource.
There are numerous resources for C in print, although many are somewhat outdated. The C Programming Language is the authoritative reference on ANSI C, written by Brian Kernighan and Dennis Ritchie, the latter of whom originally designed and implemented the langauge. Zed Shaw, author of Learn Python The Hard Way, discusses this text from a modern perspective in a chapter entitled, Deconstructing “K&R C”. The C Puzzle Book by Alan R. Feuer is an excellent resource for extending a basic knowledge of ANSI C.
A pointer is a variable that contains the address of a another variable. They usually lead to more compact and efficient code. To create a pointer simply declare it with a type as you would any variable. In addition, add the unary indirection or dereferencing operator *
in front of the name of the pointer.
int *x_p;
As with a variable, once a pointer is declared it should be initialized with some value. Instead of assigning it an integer, as with a variable, we will assign the address of a variable using the unary &
operator.
int x = 1;
= &x; x_p
In this example, the address of x
(e.g. -1081367532
) is assigned to x_p
, which means that using x_p
directly would return -1081367532
. If you would like to use the value 1
then x_p
must be called with the dereferencing operator (i.e. *x_p
).
int y = *x_p;
In this case, the variable y
is initialized with the value 1
. That means that the integer 1
exists twice in memory, as the value of x
and of y
. The compiler generates machine code that follows the address provided by x_p
, which leads it to the variable x
. Then it copies the value of x
and assigns it to y
.
Also note, it is possible to declare a pointer, variable and function all on one line.
double *dp, *atof();
It is important to note that the function named atof
is returning a pointer, which contains the address of a variable of type double.
Java and Python store the length of objects so that it is easily retrievable. In C the length of an array needs to be calculated. By dividing the number of bytes that make up the array by the number of bytes that make up one element of the array we can determine it’s length. This can be simplified using a macro as follows:
#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
This solution does not work for arrays on the heap or pointers generally, since sizeof()
will return the number of bytes that are used to store the pointer to the array.
The following was uncovered in a proprietary Linux device driver:
char (*param)[66] = kmalloc(sizeof(*param) * 65, GFP_KERNEL);
Note that []
have a higher precedence than *
. In this case, param
is a pointer to an array of 66 elements of type char
. In contrast, char *param[66]
would be an array of 66 elements of type pointer to char
.
Note that sizeof(*param)
would return 66 unlike if the return value of kmalloc()
were simply assigned to char *param
.
For example, the variable c
will be assigned whichever contains the greater value; a
or b
.
= (a>b) ? a : b; c
memset
The C standard library function memset
is often associated with bugs because a few common errors are made when using memset
.
The following code does not dereference the pointer, foo
, so memset
will use the size of the pointer to zero out the struct, leaving the remainder of the struct to be initialized with whatever happens to be in memory.
static inline void foo_init(foo_t *foo) {
(foo, 0, sizeof(foo) * (FOO_MAX + 1)); memset
memset
may get optimized away by the compiler if the memory it modifies does not get used again. In which case it is often encouraged to use memset_s
, which prohibits such optimizations and guarantees to perform the memory write. This is particularlly important for security critical contexts, where secure data is being removed from memory.
An alternative to using malloc()
and memset()
is to use calloc()
for arrays.
A check is required to prevent overflows. Not doing so is particularly dangerous when allocating memory because it can lead to leaking information (see Integer Overflow into Information Disclosure).
if (nmemb && size > -1UL / nmemb)
return 0;
Although unsigned integers overflow in a defined manner, returning to zero, how signed integers overflow is undefined. A 32-bit integer may increment from a maximum value of 2147483647 to -2147483648 and then -2147483647.
Robert Love’s Linux System Programming available in its entirety on Safari Books Online, Scribd and Google Books.
On January 24, 2012 I began the Unix Systems Programming course at the Harvard Extension School in Cambridge, MA. The course consists of a weekly two hour lecture and an additional weekly one hour section meeting.
Global variables that are modified in a interrupt handler should be marked as volatile
, which tells the compiler that the value can change unexpectedly and therefore should not be optimized out. Failing to mark a variable appropriately may lead to issues when processor optimizations or interrupts are enabled. See How to Use C’s volatile Keyword for more information.
Note that globals represent a clear example, but should be limited in their use and should be protected against race conditions (e.g. by using a mutex).
offsetof
provides a sample implementation that shows how casting a null pointer helps solve this problem