0

empowering creative people

C Programming for Makers

Welcome to the C Programming Workshop for Makers! Here you'll be able to follow along with our series of easily digestible videos that cover everything you'll need to know to on your way with C programming and start making awesome projects. As you progress through the workshops, you'll find helpful material next to each video - these could be code snippets, diagrams, reference tables, or links to other resources. My name is Josh, and I'm a computer scientist and maker who has been writing with C and related languages for years, so let me be your guide into the wonderful world of lower level coding.

To follow along, you'll just need a computer (I'll be explaining for Windows and Raspberry Pi), an Internet connection and a desire to learn some amazing skills.

Course outline:

If you run into any issues throughout this workshop, then please reach out to us on our forum. We're full-time makers and are here to help.

Let's get started!


CHAPTER 1: WHAT IS IT AND HOW DO I SET IT UP?

1.0 CHAPTER OVERVIEW

In Chapter 1 we cover a basic rundown of what the C language is, and how to set up your computer so you can actually start writing it. We'll then go over the first program anybody writes when they learn a new programming language, Hello World. We'll then dissect this and talk about the different elements that make up all C programs.


1.1 WHAT IS C?

In this section, we discuss what C is, how it came to be, and how it compares to the other common programming languages Arduino and Python.

C vs C++ vs C#

Even though these languages have similar names, they're vastly different.

  • C is a low-level procedural language that is compiled into small binaries, generally with the highest performance, which makes it more suitable for direct hardware control or where maximum performance is desirable. This comes at the cost of requiring the programmer to be careful as there are few safeguards. With great power comes great responsibility.
  • C++ is what's called an object-oriented language, which is a way to manipulate tightly related data and functions in blocks called objects, such as players in a game each having their own health, equipment and stats, rather than trying to maintain arrays of everybody's data. As such, C++ is commonly used in game programming and server applications, where they can benefit from a mid-level language that still has a high performance but is filled with extra features that make programming easier (and the extra file size this causes is not such an issue).
  • C# was created by Microsoft to be a Java competitor and is therefore targeted to the .NET runtime on the Windows platform. It is also object-oriented like C++ but has many safeguards added to remove the risk of breaking something in environments like web and desktop applications, where it is most often used. Having all these extra features does mean that the performance is lower than the other languages, but in the applications that it is used, this is not so noticeable.

Language levels

Shown below are examples of the same basic function written in some of the languages mentioned in the video, and as you can see the further down you go, the harder it is to write and easily understand.

Python

for a in range(1, 10)
print(a)

C#

using System;

public class TestProgram
{
	public static void Main()
	{
		int a = 1;
		int b = 10;
		while (a <= b)
		{
			Console.WriteLine(a);
			a++;
		}
	}
}

Arduino

void setup() {
  Serial.begin(9600);
  int a = 1;
  int b = 10;
  while (a <= b)
  {
    Serial.println(a);
    a++;
  }
}

void loop() {
}

C++

#include <iostream>

int main()
{
    int a = 1;
    int b = 10;
    while (a <= b)
    {
        std::cout << a << endl;
        a++;
    }
    return 0;
}

C

#include <stdio.h>

int main(void)
{
  int a = 1;
  int b = 10;
  while (a <= b)
  {
    printf("%d\n", a);
    a++;
  }
  return 0;
}

Assembly

.LC0:
.string "%d\n"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 1
mov DWORD PTR [rbp-8], 10
jmp .L2
.L3:
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
add DWORD PTR [rbp-4], 1
.L2:
mov eax, DWORD PTR [rbp-4]
cmp eax, DWORD PTR [rbp-8]
jl .L3
mov eax, 0
leave
ret

Machine Code (AKA Binary)

ff 25 12 0c 20 68 00 00
00 00 e9 e0 ff ff ff 55
48 89 e5 48 83 ec 10 c7
45 fc 01 00 c7 45 f8 0a
00 eb 18 8b 45 fc 89 c6
bf d4 05 40 00 b8 00 00
00 00 e8 cd fe ff ff 83
45 fc 01 8b 45 fc 3b 45
f8 7c e0 b8 00 00 00 00
c9 c3 66 2e 0f 1f 84

1.2 SETTING UP THE DEVELOPMENT ENVIRONMENT

The software that I'll be using during this course is Atom (with the gpp-compiler package installed) and MinGW on Windows. I'll be showing you how to set up and use these, as well as how to compile your programs on a Raspberry Pi if you want to (HINT: everything you need is already installed).


1.3 HELLO WORLD

The first program that anybody learning a new programming language is called Hello World, which simply prints that to a console window. It's also useful to check that your set-up is working correctly.

The code used in this video:

/*
  My first program prints "Hello World!"
  to the command prompt and then exits
*/

#include <stdio.h> // printf is part of this library

int main (void)
{
  printf("Hello World!\n"); // prints to the screen
  return 0; // ends program and sends a 0 back to the command promt
}

1.4 BREAKING DOWN THE HELLO WORLD

Here we'll dissect the Hello World program from the previous section, and explain the different parts of what makes up every C program and what they mean.


CHAPTER 2: C LANGUAGE STRUCTURE

2.0 CHAPTER OVERVIEW

This workshop is in progress, check back next week for updates!

Chapter 2 goes more in depth with the structure of C programs, including how to store and manipulate data, as well as a few tricks you can use in your projects.


2.1 TYPES

In this section, we discuss the different types of data you can have and the best ways to store them in your programs.

Listed below are the most common data types you will come across, as well as what you can store in them - in this case, using the MinGW compiler on a Windows PC, other platforms and compilers may change the sizes slightly (the Raspberry Pi does this!).

Type Size Range
Integer types
char 8 bits (1 byte) -128 to 127 (designed to store ASCII characters)
unsigned char 8 bits (1 byte) 0 to 255
short 16 bits (2 bytes) -32,768 to 32,767
unsigned short 16 bits (2 bytes) 0 to 65,535
int 32 bits (4 bytes) -2,147,483,648 to 2,147,483,647
unsigned int 32 bits (4 bytes) 0 to 4,294,967,295
long 32 bits (4 bytes) -2,147,483,648 to 2,147,483,647
unsigned long 32 bits (4 bytes) 0 to 4,294,967,295
long long 64 bits (8 bytes) -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
unsigned long long 64 bits (8 bytes) 0 to 18,446,744,073,709,551,615
Floating point types
float 32 bits (4 bytes) 1.17549e-038 to 3.40282e+038
double 64 bits (8 bytes) 2.22507e-308 to 1.79769e+308
long double 96 bits (12 bytes) 3.3621e-4932 to 1.18973e+4932

If you want to see if your platform is different, here is the code to recreate the table above. Don't worry too much about what's in the code (everything will be explained in due course), just copy it to a .c file on your platform and run it.

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

/*
  this is because Windows doesn't support printing numbers >4 bytes easily
  MinGW actually implements the Linux version as well, so we'll use it instead
*/
#ifdef __MINGW32__
  #define printf __mingw_printf
#endif

int main(void)
{
  printf("\tType\t\t\tSize\t\t\tRange\n");
  printf("\t\t\t\tInteger types\n");
  printf("char\t\t\t%d bits (%d bytes)\t%hhd to %hhd\n", sizeof(char) * CHAR_BIT, sizeof(char), CHAR_MIN, CHAR_MAX);
  printf("unsigned char\t\t%d bits (%d bytes)\t%hhu to %hhu\n", sizeof(unsigned char) * CHAR_BIT, sizeof(unsigned char), 0, UCHAR_MAX);
  printf("short\t\t\t%d bits (%d bytes)\t%hd to %hd\n", sizeof(short) * CHAR_BIT, sizeof(short), SHRT_MIN, SHRT_MAX);
  printf("unsigned short\t\t%d bits (%d bytes)\t%hu to %hu\n", sizeof(unsigned short) * CHAR_BIT, sizeof(unsigned short), 0, USHRT_MAX);
  printf("int\t\t\t%d bits (%d bytes)\t%d to %d\n", sizeof(int) * CHAR_BIT, sizeof(int), INT_MIN, INT_MAX);
  printf("unsigned int\t\t%d bits (%d bytes)\t%u to %u\n", sizeof(unsigned int) * CHAR_BIT, sizeof(unsigned int), 0, UINT_MAX);
  printf("long\t\t\t%d bits (%d bytes)\t%ld to %ld\n", sizeof(long) * CHAR_BIT, sizeof(long), LONG_MIN, LONG_MAX);
  printf("unsigned long\t\t%d bits (%d bytes)\t%lu to %lu\n", sizeof(unsigned long) * CHAR_BIT, sizeof(unsigned long), 0UL, ULONG_MAX);
  printf("long long\t\t%d bits (%d bytes)\t%lld to %lld\n", sizeof(long long) * CHAR_BIT, sizeof(long long), LLONG_MIN, LLONG_MAX);
  printf("unsigned long long\t%d bits (%d bytes)\t%llu to %llu\n", sizeof(unsigned long long) * CHAR_BIT, sizeof(unsigned long long), 0ULL, ULLONG_MAX);
  printf("\t\t\t\tFloating point types\n");
  printf("float\t\t\t%d bits (%d bytes)\t%g to %g\n", sizeof(float) * CHAR_BIT, sizeof(float), FLT_MIN, FLT_MAX);
  printf("double\t\t\t%d bits (%d bytes)\t%g to %g\n", sizeof(double) * CHAR_BIT, sizeof(double), DBL_MIN, DBL_MAX);
  printf("long double\t\t%d bits (%d bytes)\t%Lg to %Lg\n", sizeof(long double) * CHAR_BIT, sizeof(long double), LDBL_MIN, LDBL_MAX);
  return 0;
}

2.2 VARIABLES AND CONSTANTS

Here I'll show you how you can store and retrieve data in variables, as well as how to use constant values that won't change but will help with code readability and reduce human error.

Here is the code used in this video

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

#define CONSTANT 42

int main(void)
{
  int a; // not setting an initial value
  printf("a default value: %d\n", a);

  int b = 0; // setting an initial value
  printf("b value: %d\n", b);

  b = 2; // changing a pre-existing variable
  printf("b new value: %d\n", b);

  int c = CONSTANT + 1; // using a constant value
  printf("c value: %d\n", c);

  double d = 100.7;
  printf("d value: %f\n", d);

  double e = 1.007e-2; // scientific notation
  printf("e value: %f\n", e);

  short f = -1; // signed value into signed variable
  printf("f value: %hd\n", f);

  unsigned short g = -1; // signed value into unsigned variable
  printf("g value: %hu\n", g);

  int h = 0x2a; // hex notation
  printf("h value: %d\n", h);

  int i = 052; // octal notation
  printf("i value: %d\n", i);

  float j = 1.79769e+308; // putting a value too big into a float variable
  printf("j value: %f\n", j);

  double k = sqrt(2); // getting the return value of a function
  printf("k value: %f\n", k);

  return 0;
}

2.3 EXPRESSIONS

This lesson will follow on from the previous one, showing you how to manipulate the data you've stored in various ways, allowing you to do more than just maths. I'll also go over some pitfalls you may come across during your time coding, and how to fix them.

Operators

Operator Example Meaning
Arithmetic
= a = b Assignment
+ a + b Addition
- a - b Subtraction
* a * b Multiplication
/ a / b Division
% a % b Modulo
+ +a Positive
- -a Negative
++ ++a Prefix increment
++ a++ Postfix increment
-- --a Prefix decrement
-- a-- Postfix decrement
Comparison
== a == b Equal to
!= a != b Not equal to
< a < b Less than
<= a <= b Less than or equal to
> a > b Greater than
>= a >= b Greater than or equal to
Logical
! !a NOT a
&& a && b a AND b
|| a || b a OR b
Bitwise
~ ~a Complement a
& a & b a AND b
| a | b a OR b
^ a ^ b a XOR b
<< a << b a left shifted by b bits
>> a >> b a right shifted by b bits
Compound assignment
+= a += b a = a + b
-+ a -= b a = a - b
*= a *= b a = a * b
/= a /= b a = a / b
%= a %= b a = a % b
&= a &= b a = a & b
|= a |= b a = a | b
^= a ^= b a = a ^ b
<<= a <<= b a = a << b
>>= a >>= b a = a >> b
Others
[] a[b] element b of array a
& &a reference (get the memory address of) a
* *a dereference (get the data at memory address in) a
. a.b member b of struct a
-> a->b member b of struct at the memory address in a
() (a + b) used to group expressions for higher precedence
() a(b) call function a with parameter b
, a(b, c) used to separate parameters
? : a ? b : c ternary statement; if a is true, b, otherwise c
sizeof (type) sizeof (int) the number of char-sized units this type occupies
sizeof (variable) sizeof (a) the number of char-sized units this variable occupies
(type) value (double) a temporarily turn (cast) value into a 'type'

A quick note on prefix vs postfix

In most cases, choosing whether to use prefix or postfix won't make a difference (the most common use is just incrementing or decrementing a variable). But when combined with other statements to make more powerful or compact code, which one you choose to use does matter. For example, take the following code snippet:

int i = 0;
printf("%d\n", i++);
printf("%d\n", ++i);

When run, what prints out is:

0
2

This is because, in the case of the first printf, the value of i is taken (0) and printed, then incremented (from 0 to 1). In the 2nd printf, the value is incremented (from 1 to 2), then taken (2) and printed. Similar behaviour can be seen for decrement.

Precedence

Higher rows get evaluated first, and all operators in the same row have the same precedence and are evaluated in the order shown in the right-hand column.

Operators Descriptions Evaluation order

++

--

( )

( )

[ ]

.

->

Postfix increment

Postfix decrement

Precedence increase

Function call

Array element

Struct member

Struct member

Left to right

++

--

+

-

!

~

(type)

&

*

sizeof

Prefix increment

Prefix decrement

Positive

Negative

Logical NOT

Bitwise complement

Type cast

Reference

Dereference

Size of value

Right to left

*

/

%

Multiplication

Division

Modulo

Left to right

+

-

Addition

Subtraction

Left to right

<<

>>

Bitwise left shift

Bitwise right shift

Left to right

<

<=

>

>=

Less than

Less than or equal

Greater than

Greater than or equal

Left to right

==

!=

Equal to

Not equal to

Left to right
& Bitwise AND Left to right
^ Bitwise XOR Left to right
| Bitwise OR Left to right
&& Logical AND Left to right
|| Logical OR Left to right
? : Ternary statement Left to right

=

+=

-=

*=

/+

%=

<<=

>>=

&=

^=

|-

Assignment

Addition assignment

Subtraction assignment

Multiplication assignment

Division assignment

Modulo assignment

Bitwise left shift assignment

Bitwise right shift assignment

Bitwise AND assignment

Bitwise XOR assignment

Bitwise OR assignment

Right to left
, Comma Left to right

Here is an example of the evaluation order of a maths statement:

expression-example


2.4 SCOPES

This lesson explains what scope is, and how you can use it to your advantage in your projects.

Here is the code that was used in the video:

#include <stdio.h>

/*
  global variables are created outside a function
  and are available anywhere in the program
*/
int globalVariable = 0;
int anotherGlobalVariable = 1;

// function prototype (don't worry, this will be explained in a later chapter)
int otherFunction(int globalVariable, int otherParameter);

int main(void)
{
  // local variables are only available inside the function they are created in
  int localVariable = 2;
  int localVariable2 = 3;
  printf("localVariable value: %d\n", localVariable);

  // global variables can be accessed from anywhere in the same program
  printf("globalVariable value: %d\n", globalVariable);
  // global variables can also be changed from anywhere, so be careful
  globalVariable = 4;
  printf("globalVariable new value: %d\n", globalVariable);

  /*
    local variables that have the same name as a global variable are used
    instead of the global one
  */
  int anotherGlobalVariable = 5;
  printf("anotherGlobalVariable value: %d\n", anotherGlobalVariable);

  printf("\n");

  // run other function
  otherFunction(localVariable, localVariable2);

  printf("\n");

  // run function again to show static variable changes
  otherFunction(localVariable, localVariable2);

  printf("\n");

  // checking which variables got changed
  printf("final globalVariable value: %d\n", globalVariable);
  printf("final anotherGlobalVariable value: %d\n", anotherGlobalVariable);
  printf("final localVariable value: %d\n", localVariable);
  printf("final localVariable2 value: %d\n", localVariable2);

  return 0;
}

/*
  function parameters are seen as local variables, and follow the same naming
  rules.
  the value of localVariable from main is copied and placed in a parameter
  called globalVariable, and the value of localVariable2 from main is copied
  and placed in a variable called otherParameter.
  any changes to them don't go back to main (yet, that's in another chapter)
*/
int otherFunction(int globalVariable, int otherParameter)
{
  // local variables created in a function are deleted once the function ends
  int temporaryVariable = 6;
  printf("temporaryVariable value: %d\n", temporaryVariable);

  // unless they are made as static variables
  static int persistentVariable = 7;
  printf("persistentVariable value: %d\n", persistentVariable);

  /*
    make a change to some variables to show that the static variable will be
    maintained the next time this function is run and the other ones won't
  */
  persistentVariable++;
  temporaryVariable++;

  // you can see this is not the same value as what was created globally
  printf("otherFunction globalVariable value: %d\n", globalVariable);

  /*
    variables with same name in a different function are different variables
  */
  int localVariable = 8;
  printf("otherFunction localVariable value: %d\n", localVariable);

  /*
  trying to change a local variable outside the function it was created in
  doesn't work, even if you use the same names in both functions.
  this changes the variable in this function, not main
  */
  localVariable = 9;
  printf("otherFunction localVariable new value: %d\n", localVariable);

  /*
    this will cause a compile error, because there is no variable with that
    name in this function
  */
  //localVariable2 = 10;

  /*
    changes to parameters also don't flow back to the function that sent them
  */
  otherParameter = 11;

  // you can change global variables though
  anotherGlobalVariable = 12;

  return 0;
}

CHAPTER 3: CONTROL STRUCTURES

3.0 CHAPTER OVERVIEW

In Chapter 3 we cover control structures, which will allow you to create a program flow that is more complex, but also more useful, than we have used so far.


3.1 FLOWCHART NOTATION

In this section, I'll show you how to understand the flowcharts I'll be using to explain the structures in the rest of this chapter.

Name Description
flowchart-process A normal statement
flowchart-decision Tests an expression and gets a true/false answer

3.2 DECISION MAKING

This is where we start to use what you've learned in the previous chapters to make decisions based on your variables and expressions.

Name Flowchart Code Example Description
If flowchart-if
a = 1;
b = 5;
if (a < b)
{
  printf("a is less than b\n");
}
printf("done\n");
Tests a true/false expression and does something only if the expression is true.
If-else flowchart-if-else
a = 1;
b = 5;
if (a < b)
{
  printf("a is less than b\n");
}
else
{
  printf("a is not less than b\n");
}
Tests a true/false expression and does one thing if the expression is true, and something else if the expressions is false.
If-else if-else flowchart-if-elseif-else
a = 1;
if (a == 1)
{
printf("a is equal to 1\n");
}
else if (a == 2)
{
printf("a is equal to 2\n");
}
else if (a == 3)
{
printf("a is equal to 3\n");
}
else
{
printf("a is something else\n");
}
Tests multiple true/false expressions and works through them in order, doing only the first thing where the matching expression is true, or the last one if they're all false.
Switch
a = 1;
switch (a) {
case 1:
printf("a is equal to 1\n");
break;
case 2:
printf("a is equal to 2\n");
break;
case 3:
printf("a is equal to 3\n");
break;
default:
printf("a is something else\n");
break;
}
Same as above, in a more compact and easier to read format, with the caveat that the expressions have to be an equality check against the same subexpression. If you don't put break at the end of each case, your program will ALSO EXECUTE the next section, and so on until it reaches a break.
Ternary flowchart-ternary
a = 1;
b = (a < 2) ? (2) : (3);
printf("b = %d\n", b);
A simplified, single line if-else, with the caveat that you can only have 1 statement each for the expression's true and false results.

3.3 REPEATING OURSELVES

Here we go through the different ways to create loops in our code, and when you would use one way over another.

Name Flowchart Code Example Description
While flowchart-
a = 1;
b = 5;
while (a < b)
{
  printf("a = %d\n", a);
  a++;
}
printf("done\n");
Continues to execute while the expression being tested remains true. This may be 0 or more times.
For
for (a = 1, b = 5; a < b; a++)
{
  printf("a = %d\n", a);
}
printf("done\n");
Same as above, with the initialise, test and step statements built in (in that order). These are all optional
Infinite loop flowchart-
for (;;)
{
printf("infinitely printed\n");
}
Same as above, but when an empty test section is provided, will always be true. This example shown with all the optional sections removed, the common way to create an infinite loop.
Do-while flowchart-
a = 1;
b = 5;
do
{
  printf("a = %d\n", a);
  a++;
} while(a < b);
printf("done\n");
Same as a while, except the check happens at the end, rather than at the beginning. This means that the statements are run 1 or more time.
Break flowchart-
a = 1;
b = 5;
while (a < b)
{
  printf("a = %d\n", a);
  a++;
  if (a == 3)
  {
    printf("leaving loop early\n");
    break;
  }
}
printf("done\n");
Break is used to exit a loop or switch early. If the break is inside a loop or switch, itself inside a loop or switch, it will only exit the innermost loop or switch early.
Continue flowchart-
a = 1;
b = 5;
while (a < b)
{
  printf("a = %d\n", a);
  a++;
  if (a == 3)
  {
    printf("skipping\n");
    continue;
  }
  printf("b = %d\n", b);
}
printf("done\n");
Continue immediately finishes the current iteration of a loop, and starts the next one. In the case of a for loop, the step statement is still executed.

3.4 SOME PRACTICAL EXAMPLES OF EVERYTHING SO FAR

This is just a demonstration of what can be done with what has been covered in the course so far.


This workshop is in progress, check back next week for updates!

Welcome to the C Programming Workshop for Makers! Here you'll be able to follow along with our series of easily digestible vi...