This post is more than two years old and may contain outdated information.

Learning C in a Hurry

August 12, 2019

A short tutorial for the C programming language syntax and features in a series of small executable programs. All programs can be copy-pasted here.

In this post:


Introduction #

Hello C #

Execute with gcc hello-c.c -o _hello ; ./_hello, or gcc hello-c.c -o _hello-c.s -S -O0 -masm=intel -fno-stack-protector; cat _hello-c.s to get assembly.

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("filename: %s\n", argv[0]);
    // arguments
    if (argc > 1) {
        printf("%s\n", "argv: ");
        for (int i = 1; i < argc; i++) {
            printf("\t%s\n", argv[i]);
        }
    }
}

Memory #

Memory operations:

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

int main() {
    char name[36];
    char *description; // pointer to char without size limit

    // copy string of 35 bytes into buffer of 36 bytes
    strcpy(name, "Wolfeschlegelsteinhausenbergerdorff");

    // allocate memory dynamically (can result in fragmented memory)
    // description = malloc(10 * sizeof(char));
    // strcpy(description, "A too long text to store in only 10 bytes...");
    // printf("%s\n", description);
    // malloc(): corrupted top size
    // Aborted

    description = malloc(100 * sizeof(char));
    strcpy(description, "A too long text to store in only 10 bytes...");
    printf("%s\n", description);
    // A too long text to store in only 10 bytes...

    // free memory
    free(description);
}

Recursion #

#include <stdio.h>

// declare recursive function
int factorial(int i);
// define recursive function
int factorial(int i) {
    if (i <= 1) {
        return 1;
    }
    return i * factorial(i - 1);
}

int main() {
    printf("factorial of 12 is %d\n", factorial(12));
    // factorial of 12 is 479001600
}

Linked list #

Linked list implementation in C.

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

// element is struct with a value and a pointer to the next element
struct Element {
    int value;
    struct Element *next;
};

// allocate memory for element
struct Element *create_element(int value) {
    struct Element *element = (struct Element*) malloc(sizeof(struct Element));

    element->value = value;
    element->next = NULL;

    return element;
}

// insert
void ll_insert(struct Element **element, int value) {
    struct Element *new_element = create_element(value);
    // list is empty
    if (*element == NULL) {
        *element = new_element;
    }
    // list is not empty
    else {
        struct Element *current = *element;
        // iterate until last element
        while (current->next != NULL) {
            current = current->next;
        }
        // insert new element
        current->next = new_element;
    }
}

// remove
void ll_remove(struct Element **element, int value) {
    struct Element *current = *element;
    struct Element *previous = NULL;
    // iterate until last element
    while (current != NULL) {
        // value is found
        if (current->value == value) {
            // value is first element
            if (previous == NULL) {
                *element = current->next;
            }
            // value is not first element
            else {
                previous->next = current->next;
            }
            // free memory
            free(current);
            return;
        }
        // update pointers
        previous = current;
        current = current->next;
    }
}

// print
void print(struct Element *element) {
    struct Element *current = element;
    // iterate until last element
    while (current != NULL) {
        printf("%d\n", current->value);
        current = current->next;
    }
}

int main() {
    struct Element *ll = NULL;

    // insert
    ll_insert(&ll, 5);
    ll_insert(&ll, 10);
    ll_insert(&ll, 15);
    ll_insert(&ll, 20);
    ll_insert(&ll, 25);
    ll_insert(&ll, 30);
    print(ll);
    // 5
    // 10
    // 15
    // 20
    // 25
    // 30

    // remove
    ll_remove(&ll, 15);
    ll_remove(&ll, 30);
    print(ll);
    // 5
    // 10
    // 20
    // 25
}

Valist #

Variable argument lists in C using va_list.

#include <stdio.h>
#include <stdarg.h>

// declare function (normally in header file)
double average(int n, ...);
// define function
double average(int n, ...) {
    va_list valist; /* stdarg.h */
    // return value
    double sum = 0.0;
    // init valist for n arguments
    va_start(valist, n);
    // access arguments
    for (int i=0; i<n; i++) {
        sum += va_arg(valist, int);
    }
    // free memory
    va_end(valist);
    // success
    return sum / n;
}

int main() {
    printf("average of {1,2,3,4,5}: %f\n", average(5, 1, 2, 3, 4, 5));
    // average of {1,2,3,4,5}: 3.000000
}

Values #

Numbers #

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

int main() {
    // expressions
    printf("%d\n",
        12 + 2 * (40 / 4) + 10
    );
    // 42

    // built-ins
    printf("%f\n",
        pow(10, 2) /* math.h */
    );
    // 100.000000

    // floating-point conversion
    printf("%f\n",
        12.0 + (10 + 20)
    );
    // 42.000000

    // promote char to int
    printf("%d\n",
        '*'
    );
    // 42

    // type casting
    printf("%f\n",
        (double) 42
    );
    // 42.000000

    // integer division
    printf("%d\n",
        42 / 40
    );
    // 1

    // integer modulo (remainder)
    printf("%d\n",
        42 % 40
    );
    // 2

    // floating-point modulo
    printf("%f\n",
        fmod(42.5, 40.0)
    );
    // 2.500000

    // absolute value
    printf("%d\n",
        abs(-42)
    );
    // 42

    // rounding value
    printf("%f\n",
        round(42.42)
    );
    // 42.000000

    // increment and decrement
    int answer = 41;

    answer++;
    printf("%d\n", answer);
    // 42

    answer--;
    printf("%d\n", answer);
    // 41
}

Variables #

// filename.h
extern const int global_num; // extern makes variable global
#include <stdio.h>

#include "filename.h" // if declared in header file

const int global_num = 42;
char ch[255]; // array of chars is string
float x;
double y; // double size of float

int main() {
    printf("%d\n", global_num);
    // 42

    printf("%lu\n", sizeof(x));
    // 4
    printf("%lu\n", sizeof(y));
    // 8

    char ch[] = "this is some text";

    x = y = 1.0;
    printf("%f, %f\n", x, y);
    // 1.000000, 1.000000
}

Constants #

#include <stdio.h>

// #define is a preprocessor directive (substitute text, inlining)
#define A 100
#define B 200

int main() {
    // const is an immutable variable
    const int C = 300;
    const int D = 400;

    printf("%d\n", A);
    // 100
    printf("%d\n", B);
    // 200
    printf("%d\n", C);
    // 300
    printf("%d\n", D);
    // 400
}

Data structures #

Structs #

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

// structures hold data of different types
typedef struct {
    char chars[255];
    int numbers[10];
    double value;
} Element;

// declare function with struct as argument
void function(Element element);

int main() {
    // declare struct type
    Element element;

    strcpy(element.chars, "this is my first element");
    element.numbers[0] = 1;
    element.numbers[2] = 2;
    element.value = 3.14;

    printf("%s\n", element.chars);
    // this is my first element
    printf("%i\n", element.numbers[2]);
    // 2
    printf("%f\n", element.value);
    // 3.140000

    // specify number of bits (bit fields)
    struct {
        unsigned int x;
        unsigned int y;
    } bigger;
    struct {
        unsigned int x : 1;
        unsigned int y : 1;
    } smaller;

    printf("%lu\n", sizeof(bigger));
    // 8
    printf("%lu\n", sizeof(smaller));
    // 4
}

Arrays #

#include <stdio.h>

int main() {
    // array of doubles
    double doubles[] = { 200.0, -2.2, 1.0, 0.0 };
    printf("%f\n", doubles[1]);
    // -2.200000

    // arrays are mutable
    doubles[1] = 42;
    printf("%f\n", doubles[1]);
    // 42.000000

    // array of arrays of char with max size 10 (strings)
    char strings[][10] = { "first", "second" };
    printf("%s\n", strings[1]);
    // second

    // nd-array of ints
    int matrix[2][10] = {
        { 0, 1, 2, 3, 4 },
        { 5, 6, 7, 8, 9 }
    };
    printf("%d\n", matrix[1][2]);
    // 7

    // calculate length based on type (in bytes)
    printf("%lu\n", sizeof(doubles) / sizeof(double));
    // 4
}

Unions #

Union store different types of data in the same memory location (note that only one member can store data at a time).

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

union Data {
    int number;
    float value;
    char str[20];
} data;

int main() {
    union Data data;

    // size of union is size of largest member
    printf("%lu\n", sizeof(data));
    // 20

    // assign values
    data.number = 42;
    printf("%d\n", data.number);
    // 42

    // subsequent assignments overwrite previous values
    data.value = 12.5;
    printf("%f\n", data.value);
    // 12.500000

    printf("%d\n", data.number);
    // 1095237632 (number is overwritten)

    // strings are copied with strcpy
    strcpy(data.str, "this is some text");
    printf("%s\n", data.str);
    // this is some text

    printf("%d\n", data.number);
    // 1936287860 (number is overwritten)
    printf("%f\n", data.value);
    // 18492488542240085843427383574528.000000 (value is overwritten)
}

Control flow #

Conditionals #

#include <stdio.h>

#define ANSWER 42

int main() {
    int value = 42;

    if (value < 1) {
        // no
    } else if (value == 1) {
        // no
    } else if (value == ANSWER) {
        // yes!
        printf("%d is the answer: %d\n", value, ANSWER);
    } else {
        // no
    }
    // 42 is the answer: 42

    // single line
    printf("%s\n", (value != ANSWER) ? "no" : "this is the answer");
    // this is the answer
}

Loops #

#include <stdio.h>

int main() {
    for (int i=0; i<5; i++) {
        printf("%d ", i);
    }
    // 0 1 2 3 4

    // nested
    for (int i=0; i<2; i++) {
        for (int j=0; j<2; j++) {
            printf("(%d,%d)\n", i, j);
        }
    }
    // (0,0)
    // (0,1)
    // (1,0)
    // (1,1)

    // iterate an array of ints
    // note first number is length of array
    int numbers[6] = { 6, 1, 2, 3, 4, 5 };
    for (int i=1; i<numbers[0]; i++) {
        printf("%d ", numbers[i]);
    }
    // 1 2 3 4 5

    // iterate an array of ints without knowing length
    int items[] = { 1, 2, 3, 4, 5 };
    for (int i = 0; i < (sizeof(items) / sizeof(items[0])); i++) {
        printf("%d ", items[i]);
    }
    // 1 2 3 4 5

    // while
    int n = 0;
    while (n < 5) {
        printf("%d ", n);
        // increment
        n++;
    }
    // 0 1 2 3 4

    // do-while
    n = 0;
    do {
        printf("%d ", n);
        // increment
        n++;
    } while (n < 5);
    // 0 1 2 3 4
}

Switch #

#include <stdio.h>

enum Item {
    LOW = 10,
    MEDIUM = 20,
    ANSWER = 42,
    HIGH = 30
};

const enum Item items[4] = { LOW, MEDIUM, ANSWER, HIGH };

int main() {
    // iterate array of enums
    for (int i=0; i<(sizeof(items) / sizeof(items[0])); i++) {
        switch (items[i]) {
        case 10:
            printf("%s\n", "LOW");
            break;
        case 20:
            continue;
        case 30:
            printf("%s\n", "HIGH");
            break;
        default:
            printf("what is this? %u\n", items[i]);
            break;
        }
    }
    // LOW
    // what is this? 42
    // HIGH
}

Functions #

#include <stdio.h>

// declare function (normally in header file)
int power(int base, int x);
// define function
int power(int base, int x) {
    int result = 1;
    for (int i=0; i<x; i++) {
        result *= base;
    }
    // success
    return result;
}

int main() {
    printf("%d\n", power(2, 8));
    // 256
}

Files #

#include <stdio.h>

int main() {
    FILE *file;

    // write to file
    file = fopen("_file.txt", "w+");
    fprintf(file, "%s\n", "Text to write to file.\n Next line.");
    // close file
    fclose(file);

    // read file
    file = fopen("_file.txt", "r");

    char buffer[255];

    // stop read after first space
    // note that reading a word removes it from stream
    fscanf(file, "%s\n", buffer);
    printf("%s\n", buffer);
    // Text

    // stop read after first newline
    fgets(buffer, 255, (FILE*) file);
    printf("%s\n", buffer);
    // to write to file.
}

Misc #

Strings #

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

int main() {
    // a string is an array of chars
    char string[] = "This is some text";
    printf("%c, %c\n", string[0], string[3]);
    // T, s

    // built-ins
    printf("%c\n", toupper('a'));
    // A
    printf("%c\n", tolower('A'));
    // a
    printf("%d\n", isdigit('5'));
    // 1
    printf("%d\n", islower('a'));
    // 1
    printf("%d\n", isupper('A'));
    // 1

    // comparison (lexicographic order)
    char answer[] = "the answer is 42";
    printf("%d\n", strcmp(answer, "the answer is 42"));
    // 0 (equal)
    printf("%d\n", strcmp(answer, "the answer is 96"));
    // -5 (first argument is smaller than second)

    // length of string
    printf("%lu\n", strlen(answer));
    // 16
}

Logicals #

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

int main() {
    const bool YES = true;
    const bool NO = false;

    printf("%d\n", YES || NO);
    // 1
    printf("%d\n", YES && (YES && NO));
    // 0
    printf("%d\n", !YES);
    // 0
    printf("%d\n", !(!YES));
    // 1

    // equality
    printf("%d\n", 1 == 1);
    // 1
    printf("%d\n", 1 != 1);
    // 0
    printf("%d\n", 1 > 0);
    // 1
    printf("%d\n", 1 < 0);
    // 0
    printf("%d\n", 1 >= 1);
    // 1
    printf("%d\n", 1 <= 0);
    // 0
}

Errors #

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

extern int errno;

int main() {
    // pointer to file
    FILE *file;
    // error
    int errnum;
    // open file that does not exist
    file = fopen("file_does_not_exist", "rb"); // error returns -1 or NULL
    if (file == NULL) {
        // store error code
        errnum = errno;

        // print error code
        fprintf(stderr, "error code: %d\n", errno);
        // error code: 2

        // perror to print string representation of error code
        perror("");
        // No such file or directory

        // strerror to print pointer to string representation of error code
        fprintf(stderr, "error opening file: %s\n", strerror(errnum));
        // error opening file: No such file or directory
    } else {
        fclose(file);
    }

    // exit codes
    if (1 == 0) {
        // exit as failure (since something must be wrong?)
        exit(EXIT_FAILURE);
    } else {
        exit(EXIT_SUCCESS);
    }
}

Headers #

// filename.h
#ifndef FILENAME_H
#define FILENAME_H

const int number = 10;

#endif
#include <stdio.h>

#include "filename.h"

int main() {
    printf("%d\n", number);
    // 10
}

Macros #

Macros replace text before compilation.

#include <stdio.h>
#include <stddef.h>

// stringify error messages
#define  error_message(error) \
   printf("error: " #error "\n")

// if not defined (used in header files to avoid multiple inclusion)
#ifndef MESSAGE
#define MESSAGE "This is the message if not already defined."
#endif

// parameterized macros
#define square(x) ((x) * (x))
#define max(x, y) ((x) > (y) ? (x) : (y))

int main() {
   // built-in macros
   printf("%s\n", __FILE__);
   // macros.c
   printf("%s\n", __DATE__);
   // Aug 21 2019
   printf("%s\n", __TIME__);
   // 01:22:18
   printf("%d\n", __LINE__);
   // 12
   printf("%d\n", __STDC__);
   // 1

   error_message("This is an error.");
   // error: "This is an error."

   printf("%s\n", MESSAGE);
   // This is the message if not already defined.

   printf("%d\n", square(2));
   // 4

   printf("%d\n", max(4,5));
   // 5
}

Pointers #

Pointers store memory addresses.

#include <stdio.h>
#include <stddef.h>

int main() {
    int num = 5;
    int *ip = NULL; // null pointer

    printf("%p\n", ip);
    // 0x0 (nil)

    // point to address of num
    ip = &num;

    printf("%p\n", &num);
    // 0x7ffdb78c2664
    printf("%p\n", ip);
    // 0x7ffdb78c2664

    // dereference (get value at address)
    printf("%d\n", *ip);
    // 5
}