ruk·si

🔹 C
Basics

Updated at 2014-03-20 12:27

This guide contains tips how to write C code.

C is a structured programming language, used for its performance these days. It is one of the lowest level languages that a modern day programmers might encounter.

Architecture

Header files end with .h. Headers contain declarations and usage comments.

int function_name(int number);

Implementation files end with .c. Implementation files contain definitions and implementation comments.

int function_name(int number)
{
    printf("Hello there %d!", parameter);
    return 0;
}

You should have one header for a collection of related .c files. Not one header per .c file.

Programs start from the main function. The main function returns an integer that defines if an error occured.

#include <stdio.h>

int main()
{
    printf("Hello World!");
    return 0;
}

Use #include to import code outside of the current file. Include will replaces a copy of the specified file in the place of the directive, used to include header files. There are two forms, quoted and angle-bracket form. In short, angle-bracket form is for system header files while quoted form is for header files of your own program.

// Angle-bracket form:
#include <library.h>

// Quoted form:
#include "filename.h"

static functions in headers are always hacks. Not all hacks are bad, just keep it in mind.

Use inline and restrict rarely, if ever. If function call overhead is your bottleneck, you've probably done something wrong. Don't deal with buffers one byte at a time.

Code Layout

Choose a formatting style and stick to it. There are as many formatting styles as there are programmers. Whichever you use is up to you to decide but be consistent inside each project.

/**
 * Allman Extreme
 *
 * Main focus is to keep the most relevant information as far left on
 * the screen as possible, like the normal Allman does.
 *
 * Having everything on the left increases skimming speed because people
 * read top->down and left->right and you do not need to read each
 * single line to the end.
 *
 * All opening braces `{` go on a new line for consistency.
 *
 * Everything inside a scope `{}` is indented to emphasis logical structure,
 * except for switch-statements.
 *
 * Parentheses `()` stay as close to the containing statement as possible.
 */

Prefer 4 space indention over tabs. The most important things is to be consistent within the project.

Limit lines to 80 characters. More about the limit in master coding guide.

Naming

Almost everything is in snake_case. Including file names, variables, structures and custom types. Macros defining constants and values in enums are in UPPER_SNAKE_CASE. Enumeration values are prefixed by a common identifier.

#include <stdio.h>

#define CONSTANT 0x12345

typedef enum
{
    CARD_SUIT_CLUBS,
    CARD_SUIT_DIAMONDS,
    CARD_SUIT_HEARTS,
    CARD_SUIT_SPADES
} card_suit;

int my_int = 0;

typedef short int card_value;

typedef struct
{
   card_suit suit;
   card_value value;
} card;

int main()
{
    card my_card;
    my_card.suit = CARD_SUIT_HEARTS;
    my_card.value = 7;
    printf("My Card Suit: %d\n", my_card.suit);
    printf("My Card Value: %d\n", my_card.value);
    return 0;
}

Pointer and reference operators go next to the type name, when possible. Not next to the symbol name. Rationale is that the symbol name does not change, the type changes.

// bad
int *my_int = 0;

// good
int* my_int = 0;

Symbol name descriptiveness depends on the scope it is being used. Local variable used as a loop counter in a 3 line loop can be named i. Global variables must be more descriptive tha foo.

// bad
typedef struct
{
    // ...
} vps_t;
vps_t* mcnt;

// good
typedef struct
{
    // ...
} virtual_container;
virtual_container* main_container;

Comments

Prefer // for the most comments. Only available in C99 and later though.

// single-line comment

Use /* */ for comments that go multiple lines.

/*
 * Multi-line comments look like this. They work in C89 as well.
 */

Data Types

Basic data types.

// Characters are 1 byte.
// Characters are often used as numbers.
char my_char = 0;

// Shorts are usually 2 bytes.
short my_short = 0;
unsigned short my_ushort;

// Integers are usually 4 bytes.
int my_int = 0;
unsigned int my_uint;

// Longs are usually between 4 and 8 bytes
long my_long = 0;
unsigned long my_ulong;

// Long longs are guaranteed to be at least 64 bits
long long my_long_long = 0;
unsigned long long my_ulong_long;

Use signed char for strings and unsigned char for buffers. Compiler warnings will catch accidental conversions.

Avoid using floats. Might sound crazy but this is actually a good practice. Floats are dangerous in C, it's better just to avoid using them internally. Of course you might have to use them here and there like in user input and output.

Use !! when converting to bool.

Never explicitly compare booleans to true or false. false is unnecessary and true is incorrect.

Character literal.

// Character literals are quoted with single quotes '.
char my_char = 'y'; // same as `char my_char = 121;`

Hex literal, used frequently with bit twiddling.

int i_hex = 0x04;   // 4
int a_hex = 0x0F;   // 15
int the_hex = 0x10; // 16

You can get number of bytes required to present a variable with sizeof.

sizeof(int) // => 4 on most machines

// Note that if content of sizeof() is an expression, it is not evaluated.
int a = 1;
size_t size = sizeof(a++);
printf("sizeof(a++) = %zu where a = %d\n", size, a); // Still 1.

typedef can be used to create own type aliases.

typedef int my_type;
my_type some_value = 0;

Casting is done with (type)variable. Casting attempts to preserve the numeric value.

int number = 100;
printf("%d\n", number);
printf("%d\n", (char)number);
printf("%d\n", (short)number);
printf("%f\n", (float)number);
printf("%lf\n", (double)number);

Casting will overflow without a warning.

printf("%d\n", (unsigned char) 255); // => 255
printf("%d\n", (unsigned char) 256); // => 0

You can get maximal values of chars from limits.h.

#include <stdio.h>
#include <limits.h>

int main()
{
    printf("%d\n", CHAR_MAX);  // char max, e.g. 127
    printf("%d\n", SCHAR_MAX); // signed char max, e.g. 127
    printf("%d\n", UCHAR_MAX); // unsigned char max, e.g. 255
}

Data Structures

Array size must be specified on declaration.

// This array reserves 1 * 20 = 20 bytes.
char my_char_array[20];
printf("sizeof 20 length char array = %zu\n", sizeof my_char_array);
// This array reserves 4 * 20 = 80 bytes on most machines.
int my_int_array[20];
printf("sizeof 20 length int array = %zu\n", sizeof my_int_array);

You can initialization an array with values.

int my_array[10] = {6, 6};
printf("%d\n", my_array[0]); // => 6
printf("%d\n", my_array[1]); // => 6
printf("%d\n", my_array[2]); // => 0
my_array[2] = 5;
printf("%d\n", my_array[2]); // => 5

Strings are arrays of characters. Strings are terminated by a NUL byte (0x00), which is represented as '\0' in strings.

char my_string[20] = "Hello World!";
printf("%s\n", my_string);
printf("%d == %c\n", my_string[11], my_string[11]); // 33 == 1
printf("%d == %c\n", my_string[12], my_string[12]); // 0 ==
printf("%d == %c\n", my_string[13], my_string[13]); // 0 ==

There are variable-length arrays in C99 and C11. VLA length is not a compile time constant.

Control Structures

If-statement.

if (conditionOne)
{
    printf("I am never run\n");
}
else if (conditionTwo)
{
    printf("I am also never run\n");
}
else
{
    printf("I print\n");
}

Switch-statement. Do not indent the cases.

int number = 3;
switch (number)
{
case 1:
    printf("One!");
    break;
case 2:
    printf("Two!");
    break;
case 3:
case 4:
    printf("More! ");
    // No break, will go to default also!
    // A bad practice, always include break!
default:
    printf("Dunno!");
    break;
}

For-loop.

int i;
for (i=0; i < 11; i++)
{
    printf("%d\n", i); // => 0 - 10
}

While-loop.

int i = 0;
while (i < 11)
{
    printf("%d\n", i++); // => 0 - 10
}

Do-while-loop.

int i = 0;
do
{
    printf("%d\n", i); // => 0 - 10
} while (++i < 11);

Functions

Functions are pass-by-value by default.

int add_two_ints(int x1, int x2)
{
    return x1 + x2;
}

Passing pointers allows mutating the arguments.

void str_reverse(char* str_in)
{
    char tmp;
    int ii = 0;
    size_t len = strlen(str_in); // `strlen()` is part of the c standard library
    for (ii = 0; ii < len / 2; ii++)
    {
        tmp = str_in[ii];
        str_in[ii] = str_in[len - ii - 1]; // ii-th char from end
        str_in[len - ii - 1] = tmp;
    }
}

Structs

struct is a record-style collection of data. You can also declare pointers to a struct.

#include <stdio.h>

// This is structure alias definition syntax that I use.
typedef struct
{
    int width;
    int height;
} rectangle;

int main()
{
    rectangle my_rectangle;
    my_rectangle.width = 10;
    my_rectangle.height = 20;
    printf("W:%d, H:%d.\n", my_rectangle.width, my_rectangle.height);

    rectangle* my_rectangle_ptr = &my_rectangle;
    (*my_rectangle_ptr).width = 30;
    my_rectangle_ptr->height = 33; // shorthand
    printf("W:%d, H:%d.\n", my_rectangle.width, my_rectangle.height);
    return 0;
}

You should pretty much always use -> instead of ..

Use pointers to pass larger structs. For large structs, it is good to pass by pointer to avoid copying the whole struct.

int area(const rectangle* r)
{
    return r->width * r->height;
}

// called with: area(&my_rectangle)

Pointers

Pointer is a variable declared to store a memory address. Its declaration also tells you the type of the date it points to.

int x = 666;
int* px; // * is used to specify that the variable a pointer.
px = &x; // & is used to fetch the address from a variable.
printf("%p\n", (void *)px); // Prints a memory address.
printf("%zu\n", sizeof(px)); // Prints 8.

* is also used to dereference the pointer target. Note that dereferencing memory that you haven't allocated has unpredictable behaviour.

int x = 666;
int* px;
px = &x;
printf("%d\n", *px); // => 666

Changing the value the pointer is pointing to.

int x = 666;
int* px = &x;
(*px)++;
printf("%d\n", *px); // => 667
printf("%d\n", x); // => 667

When you move a pointer from one variable to another, null out the old one. Then you can unconditionally free y without problems.

// bad
px = py;

// good
px = py; py = NULL;

Pointers are incremented and decremented based on their type.

Use calloc, not malloc.

// bad, leaves the memory uninitialized
ptr = (char **) malloc (MAXELEMS * sizeof(char *));

// good, zero-initializes the buffer
ptr = (char **) calloc (MAXELEMS, sizeof(char*));

Immediate clear pointers after freeing them. Prevents use-after-free and double free. Define a macro for this e.g. FREE(&x).

// bad
free(x);

// good
free(x); x = NULL;

Assert that buffers are entirely zeroed before freeing. Don't just memset to 0, FREE each field individually. Catches memory leaks. Skip this for known safe buffers like strings.

Never write custom allocators. You are not that good, you will seriously just make it harder for your future self. Not even a simple allocator, no.

Function pointers are used to invoke functions and pass handlers around e.g. for callbacks.

// Example: use str_reverse from a pointer
void str_reverse_through_pointer(char* str_in)
{
    // Define a function pointer variable, named f.
    // Signature should exactly match the target function.
    void (*f)(char *);

    // Assign the address for the actual function (determined at runtime)
    f = &str_reverse;

    // f = str_reverse; would work - functions decay into pointers, like arrays.
    (*f)(str_in); // Just calling the function through the pointer.
    // f(str_in); // Equally valid syntax for calling it.
}

// As long as function signatures match, you can assign any function to the
// same pointer. Function pointers are usually typedef'd for simplicity and
// readability, as follows:
typedef void (*my_fnp_type)(char *);

// Then used when declaring the actual pointer variable:
// ...
// my_fnp_type f;

Debugging

Good old print.

/*
 * d = unsigned integer
 */
printf("%d\n", 0);

Assert things that are obviously invariants. Always include message with the assert. You should keep asserts on even on releases.

assert(conditional && "this thing went wrong right here")
assert(!"hey, I shouldn't be here")

__Create assert macro with string formatting.

assertf(rc >= 0, "Example error: %s", my_strerror(rc));

Sources