C Cheatsheet: A Beginner’s Notebook

Contents

Introduction

This document provides an introduction to programming in the C programming language for those familiar with programming in general and constructs such as arrays, pointers, strings, and procedures in particular. It aims to fill the gap between sole explanation of theoretical concepts and development of programs using C without knowledge of relevant pitfalls. Practical examples are given to demonstrate the presented concepts. The provided recommendations enable well-founded decisions for the application of a certain approach to solve a problem.

Memory Management in C Programs

C only provides basic automatic memory management. Therefore, code written in C is prone to memory leaks (memory is not made available for reuse when it is not needed any more) and memory corruption (e. g., by overwriting data using a pointer still pointing to already reused memory). Memory on the stack occupied by a procedure’s local variables will be freed automatically when the procedure is left.

Freeing dynamically allocated memory

Programs often acquire memory at runtime using the functions malloc, calloc, and realloc. On many architectures the memory is allocated on the heap. This memory has to be returned for reuse when it is no longer needed by calling the free function on the pointer (memory address) obtained when allocating the memory. The example program below creates a storage whose size depends on the user’s input. Then the storage is filled with numbers entered by the user. Afterwards, the storage is freed:

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

int main(void)
{
    printf("Enter the size of the storage: ");
    int size;
    scanf("%d", &size);
    if (size <= 0)
    {
        printf("Size must be greater than 0.\n");
        return EXIT_FAILURE;
    }
    int *numbers = malloc(sizeof(int) * size);
    if (numbers == NULL)
    {
        printf("Memory allocation failed.\n");
        return EXIT_FAILURE;
    }
    for (int i = 0; i < size; i++)
    {
        printf("Enter a number: ");
        int input;
        scanf("%d", &input);
        numbers[i] = input;
    }
    for (int i = 0; i < size; i++)
        printf("numbers[%d] = %d\n", i, numbers[i]);
    free(numbers);
    numbers = NULL;
    return EXIT_SUCCESS;
}

It is generally good practice to set pointers to NULL after calling free to prevent writing to the memory pointed to by the variable inadvertently. In addition, declaring the pointer variable as read-only using const t *const v both prevents losing the pointer by assigning another pointer to the variable and changing the data it points to.

In more complex data structures, a clear policy should be established regarding the responsibility of freeing memory when objects are deleted. Take a linked list consisting of nodes as an example:


// Forward declaration required because nodes store pointers to nodes.
typedef struct node node;

typedef struct node
{
    node *next;
    void *value;
};

Each instance of node stores a pointer to the next node in the list in its member next and a pointer to data assigned to the node in its member value. Implementation of this rather simple data structure raises several questions regarding memory management, for example:

  1. Can nodes only be created on the heap via designated functions of the list data structure or can they be created independent from a list for later insertion into a list?

  2. Should the list data structure free memory of nodes when they get removed from the list? This would leave invalid pointers to individual nodes stored outside the list, but the list cannot set these pointers to NULL.

  3. Should external data pointed to by the nodes’ member value be freed too upon deletion of a node? What if it is the only pointer to dynamically allocated memory available in the program?

A possible solution to the problems regarding the linked list looks like this:

Unfortunately, C does not support suitable mechanisms to enforce such policies, such as encapsulation, special reference/pointer types, garbage collection, etc. The only solution is proper documentation and careful implementation.

Recommendations

The rules for memory management of objects can be generalized:

  1. The creator (memory allocation) of an object has the sole responsibility for its deletion (freeing of memory).

  2. Objects must only be manipulated using designated manipulator functions.

  3. Pointers to foreign objects should only be stored sparingly and as long as absolutely necessary (obtain pointer, perform operation using the pointer, setting the pointer to NULL) to prevent dangling pointers after object destruction.

Preventing Memory Corruption

C does not provide protection against manipulating memory outside the memory location reserved for a certain variable. The program below demonstrates how data stored in a variable can be corrupted when using the strcpy function to overwrite a string’s null terminator and writing into the memory of another variable:

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

int main(void)
{
    char s1[] = "Daniel";
    const char s2[] = "Francis";
    printf(
        "s1 = \"%s\" (strlen: %ld, sizeof: %ld)\n"
        "s2 = \"%s\" (strlen: %ld, sizeof: %ld)\n",
        s1, strlen(s1), sizeof s1,
        s2, strlen(s2), sizeof s2
    );
    const char *const src = "Christopher";
    strcpy((char *)s1, src);
    printf(
        "s1 = \"%s\" (strlen: %ld, sizeof: %ld)\n"
        "s2 = \"%s\" (strlen: %ld, sizeof: %ld)\n",
        s1, strlen(s1), sizeof s1,
        s2, strlen(s2), sizeof s2
    );
    return EXIT_SUCCESS;
}

The two local string variables s1 and s2 are stored on the stack. When using GCC, the variables are stored in the memory right after each other (this may, however, vary between compilers and systems). The memory looks like that (»0« signifies the string’s null terminator):

Daniel0Francis0
^s1    ^s2

By copying a longer name to s1 using strcpy, the null terminator of s1 and the content of s2 get overwritten with the characters »opher« and a new null terminator. The array variables s1 and s2, however, still point to their original memory locations. The memory looks like that after calling strcpy:

Christopher0is0
^s1    ^s2

The strlen function returns the number of characters in a string without its null terminator. The sizeof operator determines the size of an array in bytes (one char is one byte). Writing over an array’s boundary does not increase its size, but changes the string’s length, as can be seen in the program’s output:

s1 = "Daniel" (strlen: 6, sizeof: 7)
s2 = "Francis" (strlen: 7, sizeof: 8)
s1 = "Christopher" (strlen: 11, sizeof: 7)
s2 = "pher" (strlen: 4, sizeof: 8)

Situations like this can be prevented by checking the destination’s length prior to writing to it:

char s1[] = "Daniel";
const char s2[] = "Francis";
const char *src = "Joseph";   // Source fits into destination, copy.
// `const char *src = "Richard";`   // Source is too long, do not copy.
if (strlen(src) < sizeof s1)
    strcpy((char *)s1, src);
else
    printf("String variable `s1` is too short.\n");
printf(
    "s1 = \"%s\" (strlen: %ld, sizeof: %ld)\n",
    s1, strlen(s1), sizeof s1
);

Recommendations

Returning Data from Functions

Passing data from functions back to the calling code is essential for procedural programming. C provides multiple methods to return data from functions, which might be more or less suitable in a certian scenario.

Values in function return values

Simple values, such as numbers, single characters, enumerations, and structures can be returned directly by value:

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

int add(const int a, const int b)
{
    int sum = a + b;   // Local variable stored on the stack.
    return sum;        // Return copy of the variable's value.
}

int main(void)
{
    int sum = add(10, 20);
    printf("sum = %d\n", sum);
    return EXIT_SUCCESS;
}

Pointers in function return values

Local variables are stored on the stack (except those marked as static) and data stored in them gets lost after the procedure is left. Therefore, local variables such as arrays or pointers to local variables must not be returned by a function, as the returned pointer will be invalid:

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

// Do not use!
char *get_string(void)
{
    char result[] = "Hello World!";   // Local variable stored on the stack.

    // GCC warning: function returns address of local variable
    //    [-Wreturn-local-addr]
    return result;
}

int main(void)
{
    char *result = get_string();
    printf("result = \"%s\".\n", result);
    return EXIT_SUCCESS;
}

Manipulate data passed in pointer parameters

To return a string from a function, usually a buffer is passed to the function and the function writes the value to the buffer or manipulates its content:

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

bool get_string(char *const buf, const size_t len)
{
    const char src[] = "Hello World!";   // 13 chars including null terminator.
    if (len <= strlen(src))
        return false;
    strcpy(buf, src);
    return true;
}

int main(void)
{
    #define BUF_LEN 40
    char buf[BUF_LEN];
    if (get_string(buf, BUF_LEN))
        printf("buf = \"%s\" (strlen+1: %ld)\n", buf, strlen(buf) + 1);
    else
    {
        printf("Buffer is too small.");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
    #undef BUF_LEN
}

Using pointers for output parameters

Output parameters can be used to pass data back to the caller of a function. These parameters hold a pointer to a variable that should receive the result. The parameters are passed by reference:

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

void add(const int a, const int b, int *const sum)
{
    *sum = a + b;   // Write result to address in memory pointed to by `sum`.
}

int main(void)
{
    int sum;
    add(3, 5, &sum);   // Pass memory address of variable `sum` to the function.
    printf("3 + 5 = %d.\n", sum);
    return EXIT_SUCCESS;
}

Allocating memory and returning pointers

As an alternative to passing a buffer to the function, the function itself can create a string on the heap instead of the stack. The function then returns the pointer to the newly created string in an output parameter result. The caller passes a variable to the function, the function allocates the string containing the result and makes the passed pointer variable point to the result string:

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

// Equivalent to `bool get_string(const char *result[const])`.
bool get_string(const char **const result)
{
    const char src[] = "Hello World!";   // 13 chars including null terminator.

    // Allocate string on the heap, the one additonal character is used for the
    // null terminator.
    char *const dst = malloc(sizeof(char) * strlen(src) + 1);
    if (dst == NULL)   // Allocation failed.
        return false;
    strcpy(dst, src);
    *result = dst;   // Make result parameter point to the string on the heap.
    return true;
}

int main(void)
{
    const char *result = NULL;
    if (!get_string(&result))   // `result` points to the string on the heap.
    {
        printf("String allocation failed.\n");
        return EXIT_FAILURE;
    }
    printf("result = \"%s\"\n", result);

    // Free the string on the heap to prevent a memory leak.
    free((void *)result);
    result = NULL;

    return EXIT_SUCCESS;
}

In some cases it might be suitable to return the pointer to the result in the function’s return value, or NULL to indicate failure:

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

char *get_string(void)
{
    const char src[] = "Hello World!";   // 13 chars including null terminator.
    char *const dst = malloc(sizeof(char) * strlen(src) + 1);
    if (dst == NULL)   // Allocation failed.
        return NULL;
    return strcpy(dst, src);
}

int main(void)
{
    const char *result = get_string();
    if (result == NULL)   // `NULL` signifies failure.
    {
        printf("String allocation failed.\n");
        return EXIT_FAILURE;
    }
    printf("result = \"%s\"\n", result);
    free((void *)result);
    result = NULL;
    return EXIT_SUCCESS;
}

Returning data using a structure

If a function should return multiple values, they can be combined in a struct. Structures are passed by value, therefore they can be returned in the function’s return value or passed back in an output parameter using a pointer. In case of larger structures (typically larger than 4 integers) using an output parameter or allocating memory for the structure on the heap should be considered:

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

typedef struct
{
    double circumference;
    double area;
}
circle_data;

circle_data calc_circle(const double r)
{
    circle_data retval;
    retval.circumference = 2 * r * M_PI;
    retval.area = pow(r, 2) * M_PI;
    return retval;
}

bool calc_circle2(const double r, circle_data *const result)
{
    if (r < 0)
        return false;
    result->circumference = 2 * r * M_PI;
    result->area = pow(r, 2) * M_PI;
    return true;
}

int main(void)
{
    const double r = 10;
    
    // Returning result in the function's return value.
    circle_data result = calc_circle(r);
    printf(
        "Circle with radius %f cm:\n"
        "Circumference: %f cm\n"
        "Area: %f cm²\n",
        r, result.circumference, result.area
    );

    // Returning result in an output parameter.
    circle_data result2;
    if (calc_circle2(r, &result2))
        printf(
            "Circle with radius %f cm:\n"
            "Circumference: %f cm\n"
            "Area: %f cm²\n",
            r, result.circumference, result.area
        );
    else
    {
        printf("Radius must be >= 0.\n");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

const in function return types

C does not support the concept of const correctness, as it is available in the C++ programming language. In C, const can be used as a qualifier of a function’s return type. Declaring the return type as const serves multiple purposes. Marking values or pointers as const enables additional compile-time checks and documents how the return values should be treated by the caller. Additionally, it allows for certain optimizations by the compiler.

Returning const to preserve the qualifier

If a value passed to a function as const is returned by the function, it can be marked as const to preserve the qualifier:

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

// Return `s` preserving the `const` qualifier.
const char *returns(const char *s)
{
    return s;
}

int main(void)
{
    const char *s = "Hello World!";
    s = returns(s);
    return EXIT_SUCCESS;
}

Returning const to indicate a value is read-only

The function get_string below returns a string literal that must not be modified as const char *. The pointer itself is not qualified as const to indicate that it can be discarded and must not be freed by the caller:

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

const char *get_string(void)
{
    return "Hello World!";
}

int main(void)
{
    const char *s = get_string();
    printf("%s\n", s);

    // Fails because `s` is `const char *`.
    //strcpy(s, "Goodbye World!");

    return EXIT_SUCCESS;
}

Returning const pointers to indicate the pointer has to be freed

The get_string function below allocates a string and returns the pointer as *const. This prevents modification of the pointer and indicates that the returned pointer has to be retained as it has to be freed by the caller of get_string:

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

char *const get_string(void)
{
    char *const s = malloc(sizeof(char) * 20);
    strcpy(s, "Hello World!");
    return s;
}

int main(void)
{
    char *const s = get_string();
    printf("%s\n", s);

    // Fails because `s` is a read-only variable.
    //s = "Goodbye World!";

    free((void *)s);
    return EXIT_SUCCESS;
}

As with function parameter types, the qualifier const can also be applied to both the type and the pointer, e. g., const char *const get_string(void). In this case calling strcpy(s, "Goodbye World!") inside main would lead to a warning because this call would discard const from the target type.

Recommendations

Iterating Over Arrays and Strings

C supports multiple ways to iterate over arrays and strings, based on language features like loops (for, while, do…while), arrays, and pointers. In most cases the decision for a certain iteration method should be based on safety and understandability of the developed source code.

Iterating over arrays

Iterating using a for loop

In order to loop over an array using a for loop, the length of the array has to be known at runtime. The naïve implementation below is suboptimal, as the array length has to be kept in sync manually in multiple places (the array length is optional in the array declaration, which can be alternatively written as const int numbers[] = { … }):

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

int main(void)
{
    const int numbers[5] = { 1, 3, 8, 2, 7 };
    int sum = 0;
    for (int i = 0; i < 5; i++)
        sum += numbers[i];
    printf("%d\n", sum);
    return EXIT_SUCCESS;
}

A preprocessor #define macro can be utilized to make the array length available where it is needed:

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

int main(void)
{
    #define NUMBERS_LEN 5
    const int numbers[NUMBERS_LEN] = { 1, 3, 8, 2, 7 };
    int sum = 0;
    for (int i = 0; i < NUMBERS_LEN; i++)
        sum += numbers[i];
    printf("%d\n", sum);
    return EXIT_SUCCESS;
    #undef NUMBERS_LEN
}

Using a constant variable like const int numberslen = 4 containing the number of elements in the array declaration const int numbers[numberslen] = { … } does not work.

The number of elements in the array can also be determined dynamically with the sizeof operator and a simple division of the total size (in bytes) of the array by the size (in bytes) of a single element of the array:

const int numbers[] = { 1, 3, 8, 2, 7 };
const size_t numberslen = sizeof numbers / sizeof numbers[0];
printf("Length of array:  %ld\n", numberslen);
int sum = 0;
for (int i = 0; i < numberslen; i++)
    sum += numbers[i];
printf("%d\n", sum);

Elements of an array can too be accessed via a pointer instead of the index, as in numbers[…]:

const int numbers[] = { 1, 3, 8, 2, 7 };
const size_t numberslen = sizeof numbers / sizeof numbers[0];
const int *p = numberslen;
int sum = 0;
for (int i = 0; i < numberslen; i++)
    sum += *p++;
printf("%d\n", sum);

Iterating over a string (array of char)

Strings in C are arrays of char that are terminated by a null character '\0' (not to be confused with the NULL pointer or EOF code used to indicate the end of a stream or file). Since char is a numeric integer type, the null terminator is simply the integer value 0.

const char s[] = "Hello World!";
int i = 0;
while (s[i])
    putc(s[i++], stdout);
putc('\n', stdout);

Iterating using a for loop

In order to loop over the characters of a string, the string’s length has to be either known or determined using the strlen function. The sample below prints a string char-by-char and ends the output by a newline:

const char s[] = "Hello World!";
const int slen = strlen(s);
for (int i = 0; i < slen; i++)
    putc(s[i], stdout);
putc('\n', stdout);

Iterating using a while loop

As strings are null-terminated in C, the last array element contains a null character (written as char literal '\0' or simply 0). This information can be utilized to iterate over a string of unknown length using a while loop. No code changes are necessary when the length of the string gets changed. The head of the while loop below can be shortened to while (c = *s++) or written as while ((c = *s++) != 0):

const char *s = "Hello World!";
char c;
while ((c = *s++) != '\0')
    putc(c, stdout);
putc('\n', stdout);

In order to obtain the pointer to the current character inside the loop, incrementation of the pointer has to be done at the bottom of the loop:

const char *s = "Hello World!";
while (*s)
{
    putc(*s, stdout);
    s++;
}
putc('\n', stdout);

However, by incrementing s we lose the pointer to the initial string. This can be fixed easily as follows:

const char *const s = "Hello World!";
const char *cp = s;
while (*cp)
    putc(*cp++, stdout);
putc('\n', stdout);
printf("%s\n", s);

Having a pointer to the current element available inside the loop is useful to manipulate the character it points to. The string must not be declared and initialized as char *s = "Hello World!" in the example below because it has to be mutable:

const char s[] = "Hello World!";
char *cp = (char *)s;
while (*cp)
{
    char c = *cp;
    if (c >= 'a' && c <= 'z')
        *cp = c - 32;   // Make uppercase.
    sp++;
}
printf("%s\n", s);

In the example above the variable cp is also visible outside the loop, which is unfortunate. This can be avoided by placing the code in an unnamed scope. Alternatively, a for loop and array brackets can be used to access and manipulate individual characters of the string:

char s[] = "Hello World!";
int slen = strlen(s);
for (int i = 0; i < slen; i++)
    if (s[i] >= 'a' && s[i] <= 'z')
        s[i] -= 32;   // Make uppercase.
printf("%s\n", s);

A for loop can even be utilized to iterate using a pointer:

char s[] = "Hello World!";
for (char *cp = s; *cp; cp++)
    if (*cp >= 'a' && *cp <= 'z')
        *cp -= 32;   // Make uppercase.
printf("%s\n", s);

The solution below works with the C compiler GCC, but not with Clang. GCC performs the pointer incrementation cp++ after executing the loop’s body, whereas Clang increments cp before its execution (when incrementation occurs is undefined behavior):

const char s[] = "Hello World!";

// Undefined behavior, do not use!
for (char cp = (char *)s, c; (c = *cp) != '\0'; cp++)
    if (c >= 'a' && c <= 'z')
        *cp = c - 32;   // Make uppercase.
printf("%s\n", s);

Declaration of Local Variables

In C local variables can be declared almost everywhere inside a function body nowadays. C99 lifted the variable declaration at top of block constraint, which enforced variable declarations directly after the opening {. The code below illustrates visibility and scope of local variables declared in various places:

void f(void)
{
    int v1;       // Compiles.
    g();
    int v2;       // Compiles.  (Did not compile prior to C99.)

    while (1)
    {
        int v3;   // Compiles.
        int v1;   // Compiles.  Note the same name as the variable `i` above,
                  //   this is called _variable shadowing_.
        g();
        int v4;   // Compiles.  (Did not compile prior to C99.)
    }

    g(v4);        // Does not compile.  `v4` only visible in the `while` loop.
    v1 = v2;      // `v1` refers to variable declared on top of the function.

    for (int v1 = 0; i < 10; i++)   // Compiles.  Variable shadowing of `v1`.
        v2 = v1;                    // Variables declared in the `for` loop's
                                    //   head are also visible in its body.

    v5 = 10;      // Does not compile.  Variables can only be used below their
    int v5;       //   declaration.

    {             // Unnamed scope.
        int v1;   // Compiles.  Variable shadowing of `v1`.
        int v6;   // Compiles.  Variable only visible in the containing block.
    }
    v6 = 10;      // Does not compile.  Variable `v6` not declared.
}

According to C99 variables cannot be declared directly after a label (case, default, or named label), as a statement is expected to follow the label. This limitation can be circumvented by placing an empty statement followed by the variable declaration or an unnamed scope containing the variable declaration directly after the label.

Recommendations

Visibility of variables should be as small as possible. Most variables should be declared in the section of code where they are used, this especially applies to helper variables and variables of minor importance. If possible, variables should be declared inside the scope where they are needed:

void f(const char s[const], const size_t len)
{
    ⋮

    // Variables `i` and `c` are only available in the `for` loop's scope.
    for (int i = 0; i < len; i++)
    {
        char c = s[i];
        ⋮
    }
    ⋮
}

Read-only Function Parameters Using const

Function parameters can be marked with the qualifier const to make them read-only. This allows for better optimization by the compiler as well as compile-time error detection. By marking a variable as read-only, its value cannot be changed directly via the variable’s name. It can only be changed intentionally after casting the variable to a non-const type. This helps in preventing erroneous manipulation of variables.

The rules for const function parameters also apply to const variables. Errors and warnings in the examples below are taken from GCC version 11.4.0.

Uses of const for function parameters

Marking value types parameters with the qualifier const makes them read-only inside the function:

void f1(const char c)   // Semantically equivalent to `void f1(char const c)`.
{

    // GCC error: assignment of read-only parameter ‘c’
    c = 'a';
}

Function parameters of pointer types can also be made const so they cannot be manipulated inside the function:

void f2(const char *s)   // Semantically equivalent to `void f2(char const *s)`.
{

    // Compiles.
    s = "Test";

    // Compiles.
    s++;

    // GCC error: assignment of read-only location ‘*s’
    *s = 'a';
}

void f3(char *const s)
{

    // GCC error: assignment of read-only parameter ‘s’
    s = "Test";

    // GCC error: increment of read-only parameter ‘s’
    s++;

    // Compiles.
    *s = 'a';
}

void f4(const char *const s)
{

    // GCC error: assignment of read-only parameter ‘s’
    s = "Test";

    // GCC error: increment of read-only parameter ‘s’
    s++;

    // GCC error: assignment of read-only location ‘*(const char *)s’
    *s = 'a';
}

Array function parameters can also be marked as const:

void f5(char s[const])
{

    // GCC error: assignment of read-only parameter ‘s’
    s = "Test";

    // GCC error: increment of read-only parameter ‘s’
    s++;

    // Compiles.
    s[2] = 'a';
}

void f6(const char s[const])
{

     // GCC error: assignment of read-only parameter ‘s’
    s = "Test";

    // GCC error: increment of read-only parameter ‘s’
    s++;

    // GCC error: assignment of read-only location ‘*(s + 2)’
    s[2] = 'a';
}

In C++ there is a convention to write the function declaration as void f(const char* const s) (pointer near the type). It can be read from right-to-left as s is a const pointer to a char that is const. More information can be found in the article Const Correctness, C++ FAQ.

Practical examples

Passing a read-only string (array of char) to a function:

char *read_file(const char *const filename)
{
    ⋮
}

Guaranteeing that values such as IDs, positions, etc. cannot be altered in the function body:

void show_record(const int id)
{

    // Erroneous assignment (`=`) instead of comparison (`==`).
    // GCC error: assignment of read-only parameter ‘s’
    if (id = 100)
        ;
}

Passing a read-only array to a function:

void show_users(const char user_ids[const])
{
    ⋮
}

Passing a read-only string to a function and looping over the characters:

  1. The type cast to non-const is optional (warning), but recommended.

  2. Pointer arithmetic using the parameter s such as while ((c = *s++) != '\0') would not work because s is declared as const char *const.

  3. The variable cp is qualified by const to indicate that it cannot be used to manipulate the characters of the string.

void prints(const char *const s)
{
    const char *cp = (const char *)s;   // <-------------------------------- (1)
    char c;
    while ((c = *cp++) != '\0')
        putc(c, stdout);
}

Note that even strings passed to a function in a const char *const parameter can be altered, just not via the parameter directly:

  1. Both const qualifiers do not make sense in the context of a toupper function.

  2. The function contains a naïve implementation for demonstration purposes only.

#include <stdlib.h>

void toupper_bad(const char *const s)   // <-------------------------------- (1)
{
    char *cp = (char *)s;
    char c;
    while ((c = *cp) != '\0')
    {
        if (c >= 'a' && c <= 'z')
            *cp = c - 32;
        cp++;
    }
}

int main(void)
{
    char s[] = "Hello World!";
    toupper_bad(s);
    printf("%s\n", s);   // Output:  "HELLO WORLD!"
    return EXIT_SUCCESS;
}

Functions Without Parameters

Functions without parameters should be defined and declared as t f(void). In C23 they can be defined and declared as t f().

Convention for Writing Pointers

The * used for pointer declarations should be written near the function or variable name to prevent visual confusion with multiplications (v1 * v2) and clarity regarding the type of individual variables in combined variable declarations (t *v1, v2 will create variable v1 of type t * and v2 of type t):

char *reverse(char *text);

int main(int argc, char **argv);

int *prev, *next;

int value = *prev;

int *prev = (int *)first;

int *next = &value;

The parameter declaration char **argv denotes a pointer to a pointer to char and is synonymous to char *argv[], which is an array of pointers to char. The parameter declarations char* argv[] and char* *argv would arguably make these semantics more obvious, but they would break symmetry to the convention used in variable declarations (t *v1[], v2[] and t **v1, v2).

In C qualifiers such as const are often written in front of the type in variable and parameter declarations, e. g., const char *const c. In the declaration const char *c1, *c2 both variables are declared as a pointer to a char that is const. const is written in front of the type because it—contrary to the pointer *—belongs to the type, not the variable (const char *c1, const *c2 does not compile). In case of constant pointers const has to be specified for every variable in a list of variable declarations, char *const c1, *const c2 declares two variables that are a const pointer to a char.

Error Handling and Resource Cleanup Pattern

The C standard library function free provided by stdlib.h has to be called to free memory allocated using functions like malloc or calloc. A sample error handling pattern is shown below.

Implementation notes

  1. The first constant in an enum will automatically have the value 0. It is generally recommendable to return 0 in case of success and another value in case of an error to allow for simple error checking using if (error) ….

  2. The variable retval defaults to SUCCESS. In case of an error an appropriate error code is assigned to the variable and returned in the code below the out label.

  3. Two labels are defined, cleanup_out and out. In case of an error the function can be left by jumping to the according label using the goto statement. This use of goto is acceptable as it is used in a recognizable pattern and jumps are only performed downwards.

  4. The function free will do nothing if NULL is passed to it. Therefore, it is superfluous to distinguish different places where free failed in the cleanup code.

Example program

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

typedef enum {
    SUCCESS,   // <--------------------------------------------------------- (1)
    ERR_INVALID_SIZE,
    ERR_MALLOC_FAILED
} result;

result f(const size_t size)
{
    int retval = SUCCESS;   // <-------------------------------------------- (2)
    if (size > 1000)
    {
        retval = ERR_INVALID_SIZE;
        goto out;   // <---------------------------------------------------- (3)
    }
    int *m1 = malloc(sizeof(int) * size);
    if (m1 == NULL)
    {
        retval = ERR_MALLOC_FAILED;
        goto cleanup_out;
    }
    char *m2 = malloc(sizeof(char) * size);
    if (m2 == NULL)
    {
        retval = ERR_MALLOC_FAILED;
        goto cleanup_out;
    }
cleanup_out:
    free(m1);   // <-------------------------------------------------------- (4)
    free(m2);
out:
    return retval;
}

void printerr(const result err)
{
    switch (err)
    {
    case ERR_INVALID_SIZE:
        fprintf(stderr, "Invalid size supplied.\n");
        break;
    case ERR_MALLOC_FAILED:
        fprintf(stderr, "Error allocating memory.\n");
        break;
    }
}

int main(void)
{
    result err = f(2000);
    if (err)   // An error occurred.
    {
        printerr(err);
        return EXIT_FAILURE;
    }
    printf("Program completed successfully.\n");
    return EXIT_SUCCESS;
}

The pattern can be generalized as follows:

result f(…)
{
    result retval = SUCCESS;
    result err;
    resource *r1 = open_resource1();
    if (err = step1())
    {
        retval = ERR_STEP1_FAILED;
        goto cleanup_step1_out;
    }
    resource *r2 = open_resource2();
    if (err = step2())
    {
        retval = ERR_STEP2_FAILED;
        goto cleanup_step2_step1_out;
    }
    resource *r3 = open_resource3();
    if (err = step3())
    {
        retval = ERR_STEP3_FAILED;
        goto cleanup_step3_step2_step1_out;
    }
cleanup_step3_step2_step1_out:
    free_resource(r3);
cleanup_step2_step1_out:
    free_resource(r2);
cleanup_step1_out:
    free_resource(r1);
out:
    return retval;
}

C Programming with the Code::Blocks IDE

Starting a program with arguments
Menu ProjectSet program’s arguments….
Changing the terminal used to launch console programs
Menu SettingsEnvironment…, section General settings, option Terminal to launch console programs. Various presets are available. The pre-configured Xterm is suboptimal as it does not allow for easy copying of text from the terminal.

Terminology used in C Programming

length
Length of an array (number of entries), length of a string (in characters).
size
Size of data in bytes, typically determined using the sizeof operator and stored in the size_t type. Size values can have a minimum (MIN) and a maximum (MAX).

Common Abbreviations Used in C Programming

Cryptic abbreviations should be generally avoided so source code can be easily read by other people. However, completely avoiding the use of abbreviations in names often leads to verbose code that distracts from its purpose.

The list below contains common abbreviations that can be safely used in C source code. (The abbreviations can be used in other programming languages too if no deviating naming conventions exist.)

In the naming convention proposed by the author, multiple words forming a name are concatenated using the underscore character (_), e. g., get_address. The underscore might be omitted if the name is short and composed of abbreviations (err_msg vs. errmsg) or a word and an abbreviation.

arg, args
argument. Used to refer to arguments, such as the command line arguments passed to a C program’s int main(int argc, char *argv[]) function.
buf
buffer. Example: Variable used as a buffer, e. g., char buf[20].
c
count. Used in variable names as a suffix to indicate that a variable contains the number of certain items, such as in the argc (argument count) parameter of a C program’s main function.
cat
category. Example: Variable term_cat, function find_cat.
cmd, CMD
command. Example: Definition #define CMD_OPEN 1.
dst, dest
destination. Example: Variable used as a destination of an operation, e. g., char dst[40].
err, ERR
error. Used as a variable containing an error code, such as err or as prefix for definitions of error codes, such as ERR_INVALID_VALUE. In the C standard library the error stream is named stderr.
ex, ext
extended, extension. Used to mark a function with extended functionality, e. g., replace_ex.
i
index. Example: Variable iend.
id
identifier. Unique identifier used to reference a certain record. Example: Variable user_id.
len
length. Example: Variable file_len, definition LEN_MAX.
max, MAX
maximum. Used in variable names, constants, or definitions, e. g., INT_MAX.
mem
memory. Example: Variable mem_size.
min, MIN
minimum. See max, MAX.
msg
message. Example: Variable err_msg.
num, NUM
number, number of items. Used for numbered items, such as the variable room_num, or in macro definitions, such as #define NUM_ROOMS 24.
opt, opts
option. Example: Variable compile_opts.
p, ptr
pointer. Used as a suffix for a variable or parameter name, such as memptr or memp, to indicate that the variable is intended for pointer arithmetic as opposed to a simple reference to an entity.
pos
position. Example: Variable start_pos.
prev
previous. Used in contexts such as prev and next to indicate the previous element.
retval
return value. Used as a variable name to store a function’s return value.
s
Plural. Appended to indicate a plural, e. g., args, opts.
src
source. Example: Variable used as the source of a copy operation, e. g., char *src = "Hello".
std
standard. Used in the C standard library, e. g., in header filenames such as stdlib.h or in stream names such as stdin.
v, val
value. Used to indicate a value, e. g., in (key, val) or (k, v) pairs, or as a suffix indicating that a variable contains values, such as the char *argv[] (argument values) parameter of a C program’s main function.
var
variable.
OnlineGDB – Online C Compiler
Develop, run, and debug C programs online.
Code::Blocks
Free and open-source IDE for C and C++ that runs on various operating systems.
C reference – cppreference.com
Compact but extensive reference for the C programming language and the C standard library.