C Programming Language

Introduction

Phases through which a C program goes to be executed

A simple program

Variables

Variable names

Some terminology

Data Types

In order of promotion hierarchy

Casting is the name given to converting type.

(newType) variableOfOldType

Casting to a lower type leads to loss of precision.

Can get finer control over the integer size by using int8_t, int16_t, int32_t, int64_t, uint8_t, etc.

Operators

Arithmetic operators

In order of reverse precedence

For convenience, C also has:

Logical operators

Bitwise operators

Control structures

Selection structures

If statement

if ( condition ){
    statement;
}
else if( condition2 ) {
    // else if is optional
} 
else if( condition3 ) {
    // can have multiple else ifs
} 
else {
    // no condition is specified
    // else is optional
}

Switch statement

switch ( variableWithDistinctValues ) {
    case value1 : 
        statement1;
        break;
    case value2 : 
        statement2;
        break;
    case value3 : 
    case value4 :
        statement3and4;
        break;
    default: 
        statementForTheRest;
        break;
}

Repetition structures

For loops

for ( initialization; loopContinuationTest; increment ) {
    statement;
}

Can have multiple initializations, increments.

e.g. for (i = 0, j = 0; j+i <= 10; j++, i+=5) { ... }

While loops

initialization;
while ( loopContinuationTest ) {
    statement;
    increment;
}

Do-while loops

Same as while, but ensures that the body of the loop is executed at least once.

do {
    statements;
} while ( condition );

Other statements

Unstructured control flow

goto (avoid, unless you really know what you’re doing and it is the cleanest approach).

    ...
    goto foo;
    ...

foo:
    ...

They are useful for - breaking out of deeply nested loops - handling errors, exceptions - cleans up opened resources - jumping to some code if allocation fails

Exceptions are used to break out of a deep call stack quickly. Similar to gotos for breaking out of loops, but here applied to function calls. setjmp, longjmp

Functions

returnType functionName(type1 parameter1, type2 parameter2,...){
    declarations;
    statements;
    return expression;
}

To call a function:

variable = functionName(argument1, argument2, ...)

If the function returns void, then

functionName(argument1, argument2, ...)

All C functions are technically pass-by-value. For the “pass-by-reference” concepts defined above, what actually happens is that a copy of the pointer is passed to the function. Incrementing the pointer inside the function, does not change the original pointer.

Function prototype

returnType functionName(type1 param1, type2 param2,...);

Header files

The C preprocessor literally inlines the contents of the included file

// lib.h
int give_me_int();
---
// main.c
#include "lib.h"

int main() {
  return give_me_int();
}

becomes

// main.c
int give_me_int();

int main() {
  return give_me_int();
}

Header guards: macro definitions that allow the compiler to only ever execute the header files the first time they are seen. It’s good practice to include them on projects of any significant size.

// stdio.h
#ifndef    _STDIO_H_
#define    _STDIO_H_

// Lots of header code

#endif /* _STDIO_H_ */

The next time stdio.h is included in the project, it will skip over all the meat of this file since _STIOD_H_ will be defined.

Standard libraries

The C standard library (or libc) provides macros, type definitions and functions for common tasks.

See https://en.wikipedia.org/wiki/C_standard_library.

Storing data

An identifier’s storage class determines:

Types:

Scope
Portion of the program in which the identifier can be referenced

Symbolic constants

#define NAME VALUE

Arrays

Declaration:

arrayType arrayName[ numberOfElements ];

Initialisation:

An initialiser list:

int a[ 5 ] = { 1, 2, 3, 4, 5 };
int b[ 5 ] = { 1 }; // remaining four elements default to value 0
int c[ ] = { 1, 2, 3, 4, 5 }; // size of c will be set to 5

Arrays and Functions:

returnType functionName(arrayType arrayArgument[], ...);

// To prevent the function from modifying the array, use const
returnType functionName(const arrayType arrayArgument[], ...);

Character arrays

char myName[] = "Anton";
// this is equivalent to the 6 characters:
char myName[] = {'A','n','t','o','n','\0'};

// and is also mostly equivalent to:
char* myName = "Anton";

e.g.

char yourName[10];
scanf("%s", yourName); // Note: no &
// This reads characters until a whitespace is encountered. 
// So if you enter "Anton Tang", yourName would just be
// Anton

Multidimensional arrays

e.g. This declares and initialises a 2 \times 2 matrix:

int my2dArray[2][2] = {{1, 2}, {3, 4}};
// my2dArray[1][0] is then 3

Enumerations

Definition:

enum EnumName { item1, item2, item3, ...};

Example:

// definition
enum Weekday { Mon, Tues, Wed, Thurs, Fri}; 

enum Weekday today; // declaration
today = Thurs; // assignment

if (today == Fri){
    printf("It's the weekend! Nearly...");
}

typedef enum WeekDay weekday_t;
weekday_t blues = Mon;

Structures

Collections of related variables (aggregates) under one name.

// definition
struct structName{
    dataType1 varName1;
    dataType2 varName2;
    ...
}; // NB: need ";" here!

// declaration and initialisation
struct structName structVar = {value1, value2, ...};
// note that above there are two parts to the struct type 

// declaring and initialing pointer to a struct
struct *sPtr;
sPtr = &structVar;

Accessing struct members

structVar.varName1 = value1;
(*sPtr).varName1 = value1;
sPtr->varName1 = value1;

Defining custom types

// Option 1
struct coord {
    double x;
    double y;
};

typedef struct coord Point;
Point pa, pb;

// without typedef
struct coord pa, pb; // annoying
// Option 2
typedef struct {
    double x;
    double y;
} Point;

Point pa, pb;

We can also define an integer pointer type typedef int * IntPtrType;

Padding/Packing

When structures are defined, the compiler is allowed to add padding (spaces without actual data) so that members fall in address boundaries that are easier to access for the CPU.

For example, on a 32-bit CPU, 32-bit members should start at addresses that are multiple of 4 bytes in order to be efficiently accessed (read and written).

struct S {
    int16_t member1;
    int32_t member2;
};

With padding:

+---------+---------+
| m1 |~~~~|   m2    |
+---------+---------+

With packing:

+---------+---------+
| m1 |   m2    |~~~~
+---------+---------+

For packed, the compiler has to generate more code (which runs slower) to access the non-aligned data members. Packed structs are suited for storing data in binary formats.

Unions

Allows for the same memory space to be interpreted as multiple types.

union myunion {
    int x;
    struct {
        char s1;
        char s2;
    } s;
};

union myunion y;

Pointers

Types of variables

Pointers:

Declaration:

pointerType *pointerName;
pointerType *pointer1, scalar;

Pointer expressions

Arithmetic operations can be performed on pointers

Pointers and arrays

Array elements can be accesed by:

The above is easy to remember once you realise that array and arraypointer are just aliases.

float f[5];
float *fptr = &f[0];
fptr++; // moves the address 4 (8) bytes forward on 32 (64)-bit machines

Using constant qualifier

const dataType variableName = variableValue;
// compiler will complain if you try to change this variable

Combinations possible

  1. non-constant pointer to non-constant data
  2. non-constant pointer to constant data
  3. constant pointer to non-constant data: must be initiliased at declaration
  4. constant pointer to constant data: must be initiliased at declaration
int *myPtr;                     // 1
const int *myPtr;               // 2
int * const myPtr = &x;         // 3
const int * const myPtr = &x;   // 4

When to use which combination?

Function pointers

int min(int a, int b);
int max(int a, int b);

int foo(bool do_min, int a, int b) {
    int (*func)(int,int); // declaration
    if (do_min)
        func = min;
    else
        func = max;
     // indirect call
    return func(a, b);
}

sizeof operator

Advanced

Inline assembly

asm("code");
asm("code": output); // asm to C
asm("code": ... : input); // C to C

References