Skip to content

C Tips and Tricks

This page contains a running list of common patterns frequently used by C programmers.

Reading from command line

The following program prints out all command-line arguments.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
        for (int i = 0; i < argc; i++) {
                printf("argv[%d]: %s\n", i, argv[i]);
        }

        return EXIT_SUCCESS;
}

$ clang -Wall -Wextra -o prog prog.c
$ ./prog one two three four

The last command prints

argv[0]: ./prog
argv[1]: one
argv[2]: two
argv[3]: three
argv[4]: four

A common usage of command-line arguments is to specify files. This following idiom, adapted from K&R, page 162, processes each file specified in command line, and if no files is specified, uses stdin instead.

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void do_stuff(FILE *fp);

int main(int argc, char *argv[])
{
        if (argc == 1) { /* no args; use standard input */
                do_stuff(stdin);
        } else {
                for (int i = 1; i < argc; i++) {
                        FILE *fp = fopen(argv[i], "r");
                        if (fp == NULL) {
                                int rc = errno;
                                fprintf(stderr,
                                        "%s: could not open %s (%s)\n",
                                        argv[i],
                                        stderror(errno));
                                exit(rc);
                        }
                        do_stuff(fp);
                        fclose(fp);
                }
        }

        return EXIT_SUCCESS;
}

malloc

SomeType x = malloc(sizeof *x);

DO NOT calculate the size by hand, such as malloc(32). There are many things wrong with this code; for examples, 1. If SomeType is a struct, adding a field in SomeType requires changes all allocation code of this struct; 2. Most primitive types in C are platform-dependent; 3. One may fail to consider struct padding.

DO NOT repeat the type name in sizeof, such as malloc(sizeof(struct SomeType)). This is much better than the previous example, but consider a change in the type of x to SomeOtherType. A change in the type of x means a change in two places: SomeOtherType x = malloc(sizeof(struct SomeOtherType)).

Zero initialization of compound types

struct SomeStruct node = { 0 }; /* zero-initialize all fields */
double array[32] = { 0 };       /* zero-initialize all elements */

This saves programmer from looping over the array or writing an assignment statement for each field.

If the value one wants to zero-initialize is in the heap, use calloc

struct SomeStruct *node = calloc(/* count */ 1, sizeof *node);
double *array = calloc(/* count */ 32, sizeof *array);

Assertion context

assert(x == y && "x is equal to y");

Unlike Python, C's assertion does not have a place to write a message. Fortunately, we can use the fact that string literals, which are non-zero pointers (of type const char *), evaluate to true when used as a boolean value. In other words, x == y && "x is equal to y" evaluates to true if and only if x == y is true, but the entire condition is displayed when the assertion fails.

Silencing unused variables

int first(int x, int y)
{
    (void) y; /* "use" y */
    return x;
}

In this class, C programs are built with -Werror, which turns warnings into errors. This trick will come in handy when you want to test-run an incomplete solution.