(embedded) C Tutorial

Table of Contents

  1. Hello World
  2. Functions
  3. Raw Types
  4. Operators
  5. Flow Control
  6. Pointers and Arrays
  7. Preprocessor
  8. Compound Data Types
  9. Keyword Reference
  10. Order of Operations
  11. What Next?

Introduction

Chapter 1: Hello World

Programming tutorials, as you will notice, always begin with the quintessential “Hello World” program which demonstrates: how to use compilers, the basic usage of the language, and how to use output which is essential for seeing if your program is working correctly. The code below is the standard C Hello World program.

//include is a compiler directive and .h refers to a header file
#include <stdio.h> //contains the prototype for printf
#include <stdlib.h> //contains the prototype for exit

//main is the symbol that the OS looks for to enter the program
int main(int argc, char * argv[]){  
     printf("Hello World\n"); //printf is part of stdio
     exit(0);
}

Note. The program above also exposes preprocessor directives, raw data types, as well as pointers and arrays. These principles are discussed in later lessons.

The program has the following output.

Hello World

Compiling Hello World

The compiler is a software program that converts C code into an executable program. Most embedded systems use cross-compilers meaning that the operating system used to compile the code is not the same as the operating system used to run the code. Stratify OS uses a cross-compiler on Mac OS X or Windows to create programs that run on embedded hardware (this tutorial has the details for doing that).

Using the word compiler is a simplification of what is actually happening. “Compiling” is a multi-step process that starts with C files and produces executable files or libraries. The compiler converts the C code (text) to object code. The object code is then either converted to a library using an archiver program or an executable using a linker program.

Process Flow for Creating Libraries

Compiler Library Flow

A library is a collection of object code files that is purposed for many different executables. For example, a library may contain code that computes a generic cosine. The library can then be used in executables that use a cosine calculation in various applications.

Process Flow for Creating Embedded Binary Files

Compiler Executable Flow

Libraries and object files are linked together to create an executable image. For desktop systems, this is the end of the process. However, most embedded systems require the executable image be translated to either a binary or hex file so that it can be loaded in embedded RAM or flash memory.

The compiler is typically a command-line based program, but most developers use an integrated development environment (IDE) to both edit the code as well as compile and run programs. Eclipse is one popular, free IDE that includes C/C++ development tools. Qt Creator is another.

Program Structure

The Hello World program introduces the modular programming structure of the C language. Programs consist of functions and data which are either native to the program or part of another module packaged in a library. The Hello World program does not declare any data and has just a single function, main(), which uses printf() and exit() which are part of separate modules from the C Standard library. The OS that runs the program uses main() as the entry point which is standard for C programming. In the example above, main() is declared with two parameters argc and argv which is the norm for desktop operating systems. For some embedded systems, main() may be declared more simply (see below) because only one program is ever installed on the system and no parameters are passed to main().

//OS doesn't pass any arguments to main(), int is returned
int main(void);

Before running main(), the OS runs startup code referred to as the C-runtime (CRT) code which initializes the program. The program then executes statements–separated by semi-colons–sequentially meaning printf() is first executed then exit() is executed.

  start=>start: Start
  end=>end: End
  crt=>operation: C Runtime (CRT)
  main=>operation: main()
  printf=>operation: printf()
  exit=>operation: exit()
  start(right)->crt(bottom)->main(bottom)->printf(bottom)->exit(right)->end

Standard Output

Hello World uses the printf() function from the stdio module. The stdio, or standard input/output, module includes functions and data useful for inputting data to the program (from the keyboard for example) and outputting data to the user (such as through a console or terminal program). Specifically, printf() writes a formatted string to the standard output. The OS determines what the standard output is. On a desktop computer, it is a console or terminal program. On an embedded system, printf() may output data to a UART serial port or over the USB–the default action for Stratify OS.

Take Away

The important things to learn here are that C is a modular programming language consisting of functions and data; the OS entry point for a C program is the main() function; the basic function used for text output is printf() which is used extensively in C during development for debugging.

Chapter 2: Functions

A function is a collection of C statements that can be utilized many times throughout a program or packaged into a module and re-used throughout many programs. The anatomy of a C function includes the prototype, the return type, the parameters, the name, and the body. Here is an example:

#include <stdio.h>
#include <stdlib.h>
int my_first_function(int x, int y); //this is the prototype
int main(int argc, char * argv[]){
     int w;
     int z;
     w = 5;
     z = my_first_function(w, 3);
     printf("z is %d\n", z);
     exit(0);
}
//this is the type (int), name, and parameters (x and y) of the function
int my_first_function(int x, int y){
     //Inside the curly brackets is the function body
     int sum;
     sum = x + y;
     x = 0;
     return sum; //tells the compiler to return the int x+y to the caller
}

The code above demonstrates my_first_function(). The prototype is always the return type, name, and parameters followed by a semi-colon. In most cases, the prototype is part of a header file so that the function can be used in many different source files. This is the case for printf() whose prototype is part of the stdio.h header file. The compiler gives a warning if a function is used, but a prototype is not found. It is important for the compiler to know function prototypes so that it can verify the number and types of the parameters passed to a function are compatible. It also checks that the return type is used correctly.

Name

The name of the function must be a valid C identifier. By convention, user-defined identifiers should not start with an underscore. It is good practice to use descriptive identifiers. Actually, my_first_function() is a poor name because it does not inform the programmer what the function does. A better name is add_xy() because the function returns the sum of the two parameters.

Note. Valid C identifiers must start with a letter or underscore and be composed of case-sensitive letters, numbers, and underscores as well as not be a [C keyword]({{< relref “2013-12-10-Keyword-Reference.md” >}}).

Parameters

The naming rules for parameters are the same for functions in that they must be C identifiers. A function can have any number of parameters passed to it of any type. The example above has two parameters of type int. Within the body of a function, a parameter acts as a local variable. It can be both read and written. However, any changes to the parameters are lost when the function returns. In the example above, the variable w within main() is not affected by the x = 0 statement within my_first_function() because a copy of w is passed to the function rather than w itself.

Body

The body of a function includes any number of C statements (separted by semicolons) within curly brackets. The return keyword is used to exit the function. If the return type is void, using return is optional. Also, return does not have to come at the end of the function. A function may conditionally return before it reaches the end. Here are a few examples to illustrate using return.

void print_output(int value){
     printf("Output is %d\n", value);
     //because the return type is void, return is optional here
}

int sum_abc(int a, int b, int c){
     if ( a < 0 ){
          return -1; //conditionally return early if a is less than 0
     }
     return a + b + c; //return the sum (as an int)
}

Take Away

A function is a primary building block in C. A function consists of a prototype, name, return type, parameters, and body. The function name as well as the parameters must be valid C identifiers consisting only of letters, numbers, and underscores. They cannot start with a number and should not start with an underscore. As a programmer, you will create, use, and re-use many, many functions.

Chapter 3: Raw Types

Programs are created to do something useful–in embedded systems, to make a device or gadget do something useful. As a means to an end, the program uses data and operations. For example, a motor’s position is represented as data in a program. The motor’s speed, also represented as data, can be calculated using various operations given the change in motor positions for a given period of time. This tutorial introduces C raw data types. The following code snippet illustrates how to use data in C and introduces the raw data type int.

#include <stdlib.h> //this declares exit()
#include <unistd.h> //this contains usleep()
#include "motor.h" //this is a fictitious header than contains get_motor_position()
int main(int argc, char * argv[]){
     /* This is where the data variables are declared--All
     variables must be declared before use Since they are
     declared inside main's {}, they are local to the
     main function */
     int motor_pos0, motor_pos1;  //commas can separate variables
     int motor_speed; //or just use a new statement

     motor_pos0 = get_motor_position();  //assign position to motor_pos0
     usleep(1000);	//wait for 1ms (1000 microseconds)
     motor_pos1 = get_motor_position();  //assign the updated motor position
     //now use the positions values and some operators to calculate the speed
     motor_speed = (motor_pos1 - motor_pos0) / 1000;
     exit(0); //exit the program
}

Data Types

Raw data types are represented by C keywords. Due to the nature of binary numbers, raw data types in C come in eight-bit chunks. Eight-bit chunks are combined to make-up the various raw data types in C: char, short, int, and long. The following is a list of the raw data types in C.

  • 8 bits wide
    • unsigned char 0 to 255
    • signed char -128 to 127
    • char either signed or unsigned depending on the implementation and the usage; typically used to represent characters in a string
  • (At least) 16 bits wide
    • unsigned short 0 to 65535
    • or unsigned short int
    • signed short -32768 to 32767
    • or short, short int, signed short int
  • (At least) 32 bits wide
    • unsigned long 0 to 4294967296 (just over 4 billion)
    • or unsigned long int
    • signed long -2147483648 to 2147483647 (about -2 billion to 2 billion)
    • or signed long int, long, long int
  • (At least) 64 bits wide
    • unsigned long long 0 to 1.8446744E+19 or 2^64 (appropriately named long, long number)
    • or unsigned long long int
    • signed long long \(-2^{63}\) to \(2^{63}-1\)
    • or signed long long int, long long, long long int
  • At least 16-bits but highly architecture dependent
    • unsigned int 0 to architecture dependent value
    • int is architecture optimized so 32-bit architectures use 32-bit integers
    • Since int is at least 16-bits, 8-bit architectures are not optimized to use int
    • signed int or int \(-2^{architecture-1}\) to \(2^{architecture-1} - 1\)
  • Floating point types
    • float single precision, 32-bits (most common on embedded architectures)
    • double double precision, 64-bits
    • long double extending precision 80-bits

The above data types can introduce ambiguity when porting code from one architecture to the next. To help eliminate the ambiguity, the C99 standard introduced the stdint.h header. It defines the following types:

  • uint8_t, int8_t: unsigned, signed 8-bit integer
  • uint16_t, int16_t: unsigned, signed 16-bit integer
  • uint32_t, int32_t: unsigned, signed 32-bit integer
  • uint64_t, int64_t: unsigned, signed 64-bit integer

These types are not raw data types but type definitions (explained later) derived from the raw data types.

The raw floating-point types must be taken in context. Most embedded processors do not natively support floating point math. This means the compiler must use software to support these types. On small, 8-bit architectures, floating point math takes up too much code space and execution time to be practical. For 32-bit systems, the code space is still substantial, but the execution time is reasonable for some applications. Even in this case, it is not wise (or even supported in some cases) to use double or long double types.

Take Away

Raw types in C come in integer and floating point types. Integers are much more prevalent on embedded systems than floating point values (though that is changing with the newest chips). The int type is architecture optimized and possibly the most commonly used type. The C99 standard introduced a header that eliminates ambiguity on integer types using int8_t, int32_t, etc.

Chapter 4: Operators

Now that we have a basic understanding of the raw data types, let’s look at the operators. You are likely already familiar with many of the C operators (unless you are still in kindergarten). The most basic are:

  • * multiply
  • / divide
  • % remainder
  • + add
  • - subtract
  • = assign value

These are straightforward operators with three quirks: first, the order of operations is imperative; second, divide does not do any rounding; third, the operators may cause an overflow depending on the data type. The code below illustrates these operators and their quirks as well as introduces formatted number printing using printf().

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main(int argc, char * argv[]){
     uint8_t x, y, z;
     x = 5; //assign 5 to x
     y = 10;
     z = x*y + 10;
     //here printf is used to display z as a decimal (base 10 value)
     printf("1.  5*10+10 is %d\n", z); //the value of z replaces %d in the string
     z = x + y * 10;
     printf("2.  5+10*10 is %d\n", z);
     z = x / y;
     printf("3.  5/10 is %d\n", z);
     z = x - 6;
     printf("4.  5-6 is %d\n", z);
     z = 13 % 2;
     printf("5.  13 % 2 is %d\n", z);
     exit(0);
}

The output of the above program is:

1.  5*10+10 is 60
2.  5+10*10 is 105
3.  5/10 is 0
4.  5-6 is 255
5.  13 % 2 is 1

The program output illustrates how these operators work.

  1. The order of operations is first to multiply then to add: (5 * 10) + 10 is 60
  2. The order of operations again is to first multiply then add: 5+(10 * 10) is 105
  3. Because 5 divided by 10 is 0 with a remainder of 5 the value assigned to z is 0. No rounding is performed.
  4. Because z is an unsigned 8-bit number (0 to 255), assigning five minus six to it causes the number to overflow to 255 (see the diagram below).
  5. z is assigned the remainder of 13 divided by two. This operation is very handy for identifying even and odd numbers.

Lines one and two both illustrate the order of operations. The operations are generally executed from left to right with multiply and divide always happening before add and subtract. The assign operator = has the lowest precedence and is executed last. Parentheses always override the default order of operations; for example, z=(x+y)*10 will first add x and y then multiply the result by 10 and assign the value to z.

Overflow Diagram

Binary Operators

In addition to the easily recognizable operators, C has addition operators that are mostly based on binary number representation. In order to understand these operations, a cursory knowledge of binary numbers is required.

A binary number uses just two symbols (0 and 1) to represent a value. This is why it is known as a base-2 numbering system. The classical number system most people are familiar with uses ten symbols (0 to 9) known as base-10 or decimal numbering. To understand binary, we need to take a closer look at the decimal system.

When we count in the decimal system, we go through each of the ten symbols (usually skipping zero). When we hit the last symbol (the number 9), we go back to zero and add another column. Each symbol in the new column represents a number ten times greater than the last column. The counting below illustrates this:

0, 1, 2 … 9, 10 (add a column and restart the symbols), … 98, 99, 100 (add a column and restart), 998, 999, 1000 (add a column and restart) If we recall learning to count, we learned the first column is known as the one’s column, then the ten’s column, etc. If we apply this logic to binary, where there are only two symbols, we count like this:

0, 1, 10 (add a column and restart), 11, 100 (add a column and start over), 101, 110, 111, 1000 (add a column and restart) Instead of the one’s, ten’s and hundred’s column, each new column is just two times greater yieding the one’s, two’s, four’s, eight’s, and sixteen’s column. The table below shows binary numbers from 0 to 15.

Decimal Binary
0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001
10 1010
11 1011
12 1100
13 1101
14 1110
15 1111

When looking at binary numbers, you can convert to decimal by adding the value of the column for the columns with a one. Take 12 for example. It is written 1100 in binary. The column values are 8-4-2-1. There are ones in the 8 column and the 4 column and 8+4 is equal to 12. For the binary number 7 (111), we add 4+2+1 to get 7.

Armed with an understanding of binary numbers, the binary operators in C should come easily. The operators include “shift”, “and”, “or”, “xor”, and “not”. A shift comes in left and right varieties while the others can be either bit-wise or logical. The following code illustrates shifting.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main(int argc, char * argv[]){
    uint8_t x, y, z;
    x = 5; //this is a binary 00000101
    z = x >> 1; //this mean shift x right one bit and assign to z
    printf("1.  5>>1 is %d\n", z);
    y = 2; //binary 00000010
    z = y << 3;  //shift y left 3 bits and assign to z
    printf("2.  2<<3 is %d\n", z);
    //shifting also has to deal with overflow
    x = 128;
    z = x << 2; //shift x left 2 bits and assign to z
    printf("3.  128<<2 is %d\n", z);
    exit(0);
}

The above program produces the following output.

1. 5>>1 is 2
2. 2<<3 is 16
3. 128<<2 is 0

Looking at the output above:

Five (0101) shifted to the right drops the one’s column to get binary 2 (010). Shifting one bit to the right is the same thing as dividing by 2. This is analogous to decimal numbers. If you shift a decimal number to the right 1, you are dividing by 10. Likewise, shifting right 2 bits divides by 4 and shifting right 3 bits divides by 8 and so on.

Two (010) becomes 16 (010000) when shifted to the left 3 bits. Since shift to the right is the same as dividing, shifting one bit to the left multiplies by 2; two bits multiplies by 4; three bits multiplies by 8; and so on. In this example, 2 shifted left 3 bits is equal to 2 times 8 or 16.

If a bit is shifted out of the data type, it is dropped. 128 (10000000) shifted left one becomes zero because the bit shifts out of the 8 bits because z is a uint8_t. If z were a uint16_t, the new value for z would be 256.

The next operators are the bit-wise and logical “or”, “and”, “xor”, and “not”. The bit-wise “or” operator is represented by | (it is the one on the same key as \). If any of the inputs are one, the output is one.

OR (|)

x y z
0 0 0
0 1 1
1 0 1
1 1 1

The bit-wise “and” operator is represented by the & symbol. For each bit, if both input are one, then the output is one. If any of the input is zero, the output is zero.

AND (&)

x y z
0 0 0
0 1 0
1 0 0
1 1 1

For “xor”, represented by ^, the output is one if exactly one input is one. A truth table is typically used to document the output of binary bit-wise operators. The following is the truth table for & where x and y are inputs and z is the output.

XOR (^)

x y z
0 0 0
0 1 1
1 0 1
1 1 0

The bit-wise “not” operator is denoted in C using the ~. It only has one argument. The output changes all the zeros to ones and vice-versa.

The logical versions of “and”, “or”, and “not” assume the inputs are either zero or non-zero and output one or zero accordingly. The symbols are &&, || (two, consecutive vertical lines), and ! respectively. The code example below illustratres how to use these operators.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main(int argc, char * argv[]){
    uint8_t x, y, z;
    x = 5;
    y = 7;
    z = x &amp; y;  //x and y are bit-wise and'ed then assigned to z
    printf("1.  5&amp;7 is %d\n", z);
    z = x | y; //x and y are bit-wise or'ed then assigned to z
    printf("2.  5|7 is %d\n", z);
    x = 0;
    y = 10;
    z = x &amp;&amp; y; //z is the logical and of x and y
    printf("3.  0&amp;&amp;10 is %d\n", z);
    x = 0;
    y = 10;
    z = x || y; //z is the logical and of x and y
    printf("4.  0||10 is %d\n", z);
    z = 1;
    printf("5.  z is %d !z is %d\n", z, !z);
    exit(0);
}

The output of the above program is:

1.  5&7 is 5
2.  5|7 is 7
3.  0&&10 is 0
4.  0||10 is 1
5.  z is 1 !z is 0
  1. This is a bitwise “and” of 0b00000101 (5) and 0b00000111 (7). Since bits 0 and 2 are one in both input values, they are both one in the output value of 0b00000101 (5).
  2. For the bitwise “or” of 0b00000101 (5) as well as 0b00000111 (7), the output is 0b00000111 because these bits are set in either of the inputs.
  3. For the logical “and” operation, the output is always zero or one. The inputs are considered non-zero or zero. A non-zero input acts as a one where a zero input acts as a zero. # # # Since x is zero, the output is zero.
  4. The logical “or” treats the inputs in the same manner as the logical “and” but outputs a one because y is a non-zero input.

For logical “not”, the output is zero if the input is non-zero; the opposite is also true.

Comparison Operators

We are going back to some basic operators with which you are already familiar. These operators (like logical “and”/“or”) only output a one or a zero. They are typically used in program flow control, which is covered later, rather than just doing math. Here are the operators:

  • == is one if the two arguments are equal; zero otherwise
  • > is one if the left argument is greater than the right
  • >= is one if the left argument is greater than or equal to the right
  • < is one if the left argument is less than the right
  • <= is one if the left argument is less than or equal to the right

Note A single equal sign = is an assignment operator and a double equal sign == is a comparison operator.

The following program demonstrates how these work.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main(int argc, char * argv[]){
     uint8_t w, x, y, z;
     w = 5; //assigns 5 to w
     x = 5; //assigns 5 to x
     y = 10; //assigns 10 to y
     z = x == y; //if x is equal to y, z is assigned one otherwise z is assigned zero
     printf("1.  5==10 is %d\n", z);
     z = w == x; //if x is equal to w, z is assigned one otherwise z is assigned zero
     printf("2.  5==5 is %d\n", z);
     z = x > y; //if x is greater than y, z is assigned one otherwise z is assigned zero
     printf("3.  5>10 is %d\n", z);
     z = x < y; //if x is less than y, z is assigned one otherwise z is assigned zero
     printf("4.  5<10 is %d\n", z);
     z = x <= w; //if x is less than/equal to w, z is assigned one otherwise z is assigned zero
     printf("5.  5<=5 is %d\n", z);
     z = w >= y; //if w is greater than/equal to y, z is assigned one otherwise z is assigned zero
     printf("6.  5>=10 is %d\n", z);
     exit(0);
}

The output of the program is:

1.  5==10 is 0
2.  5==5 is 1
3.  5>10 is 0
4.  5<10 is 1
5.  5<=5 is 1
6.  5>=10 is 0

These operators should be pretty straightforward. There is one thing to remember. It is bad practice to use == with a floating point value. It is better to bound the range using something like

((x > 2.0) && (x < 4.0))

Notice how the logical “and” && is used in conjuction with the other operators. Combining operators is really where these become powerful tools to programmers.

Combining Operators

When combining operators, the order of execution is critical for getting the desired result. The order of operations is mentioned briefly above in the *``/ and +``- section and is equally important for the binary and comparison operators.

When combining operators, things can get confusing when trying to deduce the order of operations. It is good programming practice to use copious amount of parentheses to make things clear both to you and to future maintainers of your code. The program below illustrates both good and bad use of parentheses when combining operators.

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]){
    int a;
    int b;
    int c;
    a = 5;
    b = 10;
    c = 15;
    //Here is a bad example because we rely soley on order of operations
    if ( a + b  10 &amp;&amp; b + c  100 ){
       printf("The bad example is true\n");
    }
    //Here is a better example that uses parentheses
    if( (a + b  10) &amp;&amp; (b + c  100) ){
       printf("This is a better example\n");
    }
    //This example furthe clarifies but may be onrous to program
    if ( ((a+b) < 10) &amp;&amp; ((b+c)  100) ){
        printf("This example leaves no room for error\n");
    }
    return 0;
}

Shortcut Operators

The C language provides a number of shorthand ways to do some operations. One of the most common is ++ which increments a variable.

#include <stdio.h>
#include <stdlib.h>
int main(int argc; char * argv[]){
     int x;
     x = 0;
     printf("x is %d\n", x++);
     printf("x is now %d\n", ++x);
     exit(0);
}

The code above demonstrates both post-increment and pre-increment short cuts. It outputs:

x is 0
x is now 2

The x++ statement increments x after its value is passed to the printf() function while ++x increments the variable before it is passed to printf().

Here is the full list of shortcuts plus their equivalents:

x++; //(post-increment) equivalent to x = x + 1
++x; //(pre-increment) equivalent to x = x + 1
//post/pre increment have different rules for the order of operations
x--; //equivalent to x = x - 1
--x; //equivalent to x = x - 1
x+=y; //equivalent to x = x + y
x-=y; //equivalent to x = x - y
x*=y; //equivalent to x = x * y
x/=y; //equivalent to x = x / y
x%=y; //equivalent to x = x % y
x|=y; //equivalent to x = x | y
x&=y; //equivalent to x = x & y
x^=y; //equivalent to x = x ^ y
x>>=y; //equivalent to x = x >> y
x<<=y; //equivalent to x = x << y

Take Away

The C language includes many mathematical operators for doing basic arithmetic, comparisons, and binary operations. The order of operations is critical in C for correctly evaluating expressions. Sometimes understanding this order can be difficult when skimming through code, but adding parentheses can help to make code easier to read and avoid unintended evaluation orders.

Chapter 5: Flow Control

The typical C program executes one statement after the next. Various mechanisms exist to vary the flow of the program in order to conditionally execute some statements while executing others over and over within a looThe C flow control keywords include:

  • if, else
  • switch, case
  • default
  • goto, label
  • do, while, or for
  • break, continue

The first three groups are associated with conditional execution while the last group is used to execute the same group of statements within a loop.

If, Else

The if and else keywords allow you to conditionally execute a group of statements based on certain conditions. The syntax includes the if keyword followed by an expression in parentheses and a statement to execute. If the statement includes multiple statements, curly brackets are used. Here is an example:

if ( x == 5 ) {
     printf("x is equal to 5\n");
}
//OR
if ( x == 5 ){
     printf("x is equal to 5\n");
} else {
     printf("x is NOT equal to 5\n");
}

The first if clause will print x is equal to 5 if the variable x is equal to five. Otherwise, it will not output anything. The second if clause appends a statement to execute if x is not equal to five. The else portion of an if clause is always optional. If you are only executing one statement within an if clause, the curly brackets {} are also optional. Though, many programmers always include them to make the code more readable and to avoid ambiguous situations. Here are a few more examples.

if(x>5) printf("x is greater than 5\n");
//there is a pitfall to omitting {}, it can create ambiguity with compound if statements
if(x>5)
     if(x10)
          printf("x is greater than 5 and x less than 10\n");
     else
          printf("x is not less than 10 but it might be greater than 5\n);

In the snippet above, it is not clear whether the else statement is attached to the first or second if statement. Using the curly brackets fixes the ambiguity.

The above examples are simple, but if statements can be rather complex. Any combination of symbols and operators can be used.

if ( (x + 5) / (y + 10) && ((x  100) ){
     //statement
}
//sometimes logical and/or are separated on a newline for clarity
if( (x > 5) &&
     (x < 10) &&
     ((y  200)) ){
     //statement
}

Switch, Case

The switch/case flow control structure is designed to allow the user to execute a statement based on the value of an integer type variable. It is a shorthand version of a series of if, else statements which might look something like the following example.

if( x == 0 ){
     //case 0
} else if ( x == 1 ){
     //case 1
} else if ( x == 2 ){
     //case 2
} else if ( x == 10 ){
     //case 10
} else {
     //default
}

The equivalent code using switch and case is:

switch(x){
case 0:
     //statement
     break;
case 1:
     //statement
     break;
case 2:
     //statement
     break;
case 10:
     //statement
     break;
default:
     //statement
}

The break keyword is used to exit the switch context. Many times it is present at the end of each case. However, break can be omitted if the same statement is to be executed for multiple cases.

switch(x){
case 0:
     //statement for 0
case 1:
     //statement for 0 and 1
     break;
case 2:
case 3:
case 4:
case 5:
     //statement for 2, 3, 4, and 5
     break;
case 10:
     //statement
     break;
default:
     //statement
}

Goto, Label

The goto keyword is used to jump to another location in a program. The concept of the goto keyword has its root in assembly language which uses branches and jumps to move around the order of execution. However, the use of goto in C is somewhat taboo because it makes a program difficult to maintain and understand. Nonetheless, here is an example of using it. A label in C is a identifier followed by a colon (the_end: in the code below).

#include <stdio.h&amp;gt.h
#include <stdlib.h>
int main(int argc, char * argv[]){
     printf("start here\n");
     goto the_end;
     printf("skip over this part\n");

     the_end: //this is the label, it is following by a colon like case labels
     printf("the end\n");
     exit(0);
}

For Loop

While if/else and switch/case allow conditional execution of codes, loops allows the same code to be executed many times. C has two types of loops: for loops and while loops. The for loop includes an initial statement, a test statement, and an increment statement. The following is an example of using a for loop in a program.

#include <stdio.h&amp;gt.h
#include <stdlib.h>
int main(int argc, char * argv[]){
     int i;
     for(i=0; i < 5; i++){ //intial; test; incremental
          printf("i is %d\n", i);
     }
     exit(0);
}

The initial statement is execute before the loop is entered. The increment statement executes when the loop completes. The condition statement executes before the loop restarts. If the condition statement is a non-zero value, the loop continues executing. The following example should clear up any conclusion:

The output of the above program is shown below

i is 0
i is 1
i is 2
i is 3
i is 4

The loop in the above example executes five times. Once i is equal to five, the condition statement evaluates to zero, and the loop terminates.

While Loops

While loops execute as long as a condition is true. They can take on two formats known as the while loop and the do/while looEach loop type is shown in the following program.

#include <stdio.h&amp;gt.h
#include <stdlib.h>
int main(int argc, char * argv[]){
     int i;
     printf("while loop:\n");
     i = 0;
     while(i  5 ){
          printf("i is %d\n");
          i++;
     }
     printf("do/while loop\n");
     i = 0;
     do {
          printf("i is %d\n");
          i+;
     } while( i < 5 );
     exit(0);
}

The main difference between while and do/while is that the do/while loop will always execute at least once because the condition statement is not checked until the body statements have already been executed. Conversely, the while loop executes the condition statement before the body statements and can execute zero times if the condition statement is initially false.

Here is the output of the above program.

while loop:
i is 0
i is 1
i is 2
i is 3
i is 4
do/while loop
i is 0
i is 1
i is 2
i is 3
i is 4

Note the output of the while and do/while loops is identical to the output of the for loop in the previous example.

Take Away

Flow control in C programs is broken down to conditions and loops. C’s if/else clauses and switch/case clauses are the main tools for conditionally executing statements in C. Two mechanisms are available for loops: the for loop and the while loop.

Chapter 6: Pointers and Arrays

Pointers and arrays are powerful tools in C programming. While arrays are a straightforward concept, pointers are more difficult for new programmers to grasp. Nonetheless, they are worth learning as they are an essential part of the C language.

Conceptually arrays are lists of data, but more specifically they are an abstration of pointers. The namesake of pointers offer a clue as to what they are. Rather than be variables with a meaningful value, they “point” to variables that have a meaningful value. The value of a pointer is actually the memory address of the pointed to variable.

Arrays

An array in C is declared using brackets after the variable. In the declaration, the number inside the brackets represents how many elements are in the array. When assigning or reading values in the array, brackets are used again, but in this case, the number inside the brackets refers to which element (from zero to the total number of elements minus one) to assign or read. The example below helps to clarify how to use arrays.

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]){
     int xlist[5];  //declares an array of ten integers
     int i;

     for(i=0; i < 5; i++){
          xlist[i] = i*2; //assign i to the xlist
     }
     for(i=0; i  5; i++){
          printf("xlist[%d] = %d\n", i, xlist[i]);
     }
     exit(0);
}

The above program has the following output:

xlist[0] = 0
xlist[1] = 2
xlist[2] = 4
xlist[3] = 6
xlist[4] = 8

The example above demonstrates a one-dimensional array. In C, arrays can also be declared as multi-dimensional. Multi-dimensional arrays are stored in memory in row-major order. This means, the first row in memory is first, followed by the second row, and so on. This is contrasted to column-major order which stores the first column in memory first. The following program shows multi-dimensional array usage.

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]){
    int i, j;
    int a[2][5] = { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10} };
    int * ptr;
    ptr = (int*)a;
    for(i=0; i  2; i++ ){
        for(j=0; j < 5; j++){
            printf("a[%d][%d] = %d\n", i, j, a[i][j]);
        }
    }
    for(i=0; i < 10; i++){
        printf("0xX=%d\n", ptr, *ptr);
        ptr++;
    }
    exit(0);
}

The above program has the following output.

a[0][0] = 1
a[0][1] = 2
a[0][2] = 3
a[0][3] = 4
a[0][4] = 5
a[1][0] = 6
a[1][1] = 7
a[1][2] = 8
a[1][3] = 9
a[1][4] = 10
0x10002F90=1
0x10002F94=2
0x10002F98=3
0x10002F9C=4
0x10002FA0=5
0x10002FA4=6
0x10002FA8=7
0x10002FAC=8
0x10002FB0=9
0x10002FB4=10

Notice that when the 2x5 array is declared, it is a[<rows>][<columns>].
We use a pointer (explained below) to show where in memory each element is stored. The first row is stored in the first 20 bytes (four bytes per integer), and the second row occupies the next 20 bytes which is defined as row-major order.

Pointers

The value of a pointer is the memory address of the pointed-to variable. A pointer type is declared by inserting an asterisk between the type and the variable declaration.

int x; //x is an integer type
int * ptr; //ptr is a pointer to an integer type

When using pointers, the asterisk is prepended to the pointer to operate on the pointed-to variable. This is referred to as “de-referencing” in pointer jargon. In order to assign a pointer to an existing non-pointer variable, the ampersand & is placed in front of the to-be-pointed-to variable. Ampersand in this context is read “address of”. It is known as the “referencing” operator. The program below shows examples of these operators.

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]){
     int x; //x is an integer
     int * ptr; //ptr is a ptr to an integer
     ptr = &x; //ptr is assigned the "address of" x (ptr now "points" to x)
     x = 0; //assign 0 to x
     *ptr = 500; //This assigns 500 to the value that ptr points to (x in this case)
     printf("x = %d, *ptr = %d\n", x, *ptr);
     printf("The address of x is 0x%X which is the same as ptr (0x%X)\n",
          &x,
          ptr);
     exit(0);
}

The output of the above program is:

x = 500, *ptr = 500

The address of x is 0x10002FBC which is the same as ptr (0x10002FBC) In the program output, you will notice the actual value of the pointer makes no sense. This is because the value of the pointer is just a memory address that is managed by the OS and compiler. When the pointer is de-referenced, the value matches that of x.

Pointers and Arrays

As discussed before, arrays are lists of data. On an elemental level an array is actually a pointer with memory allocated when it is declared. The following example illustrates how an array is a pointer and can be used in the same way.

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]){
     int i;
     int xlist[5] = {0, 5, 3, 7, 8};
     int * xptr; //pointer to an integer
     xptr = xlist; //xptr points to the beginning of the list
     for(i=0; i  5; i++){
          //xptr and xlist hold the same value
          printf("xlist[%d] = %d and %d\n", i, xptr[i], xlist[i]);
     }
     //as xptr is incremented it goes through the values in xlist
     for(i=0; i < 5; i++){
          printf("*xptr = %d (value of xptr:0x%X)\n", *xptr, (int)xptr);
          xptr++;
     }
     exit(0);
}

The program above has the following output.

xlist[0] = 0 and 0
xlist[1] = 5 and 5
xlist[2] = 3 and 3
xlist[3] = 7 and 7
xlist[4] = 8 and 8
*xptr = 0 (value of xptr:0x10002FA8)
*xptr = 5 (value of xptr:0x10002FAC)
*xptr = 3 (value of xptr:0x10002FB0)
*xptr = 7 (value of xptr:0x10002FB4)
*xptr = 8 (value of xptr:0x10002FB8)

There are a few important concepts to get from this program. The value of an array variable is actually the memory location of the first element. The bracket [] operators dereference from the beginning of the array based on the value in the brackets and the size of the element type, such as four for an int on a 32-bit processor. Likewise when a pointer is incremented, it increments by the size of the pointed-to-variable. Since xptr above points to type int, it is incremented by four bytes each time it is incremented. To make this point extra clear, assume xptr points to an int and sizeof(int) is equal to four, the statement xptr = xptr + 1 will increase the value of xptr by four which is the size of one int.

Pointers and Strings

Strings are one of the most common usages of pointers (especially for beginning programmers) in C. Strings can either be declared as an array of characters or a pointer to a character type. You can read or modify strings using either the pointer or array notation. A C string is always terminated with zero. This means an array of 16 bytes can hold a string that is 15 bytes long followed by a zero.

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]){
     char my_string[16]; //string of up to 15 bytes
     my_string[0] = 'H';
     my_string[1] = 'e';
     my_string[2] = 'l';
     my_string[3] = 'l';
     my_string[4] = 'o';
     my_string[5] = 'W';
     my_string[6] = 'o';
     my_string[7] = 'r';
     my_string[8] = 'l';
     my_string[9] = 'd';
     my_string[10] = 0; //the terminating zero
     printf("My strings is:%s\n", my_string);
}

In the program above, an array of char’s is declared. Any array of char’s that ends with a zero is a valid string in C. We also introduce the %s escape sequence, which is used to insert a string when using printf(). Additionally, the C standard library contains a string module with many commonly used string manipulation functions.

Pointers and Struct’s

Pointers are also commonly used when a function has too many parameters to be practical. The parameters are defined in a struct then a pointer to the struct is passed to the function. This approach has the advantage of not using an excessive number of arguments, and it allows the function to read and write the data. The following program illustrates this concept.

#include <stdio.h>
#include <stdlib.h>
struct xyzabc {
     int a;
     int b;
     int c;
     int x;
     int y;
     int z;
}
int many_parameters(int x, int y, int z, int a, int b, int c);
void few_parameters(struct xyzabc * params);
int main(int argc, char * argv[]){
     struct xyzabc var1;
     int result;

     result = many_parameters(1, 2, 3, 7, 8, 9);
     var1.a = 7;
     var1.b = 8;
     var1.c = 9;
     var1.x = 1;
     var1.y = 2;
     var1.z = 3;
     few_parameters(&var1);
     result = var1.c;

     return 0;
}
int many_parameters(int x, int y, int z, int a, int b){
     return a*b + x*y*z;
}
void few_parameters(struct xyzabc * params){
     params->c = params->a*params->b + params->x*params->y*params->z;
}

Take Away

Arrays and pointers are powerful tools in C. Arrays store lists or matrices of data while pointers access the memory of a variable rather than the variable itself. Though pointers can be difficult to master; it is very important that new programmers take the time to learn them for two reasons. First, they are used a lot in the standard C library and through many available libraries. Second, they can cause problems (crashing) if used incorrectly.

Chapter 7: Preprocessor

The C language preprocesses all code before compilation. Within the program, preprocessor directives give special instructions to the preprocessor. Preprocessor directives in C start with the hash symbol (#). This tutorial describes the basic function of preprocessor directives in C. The following directives are available:

  • #include: include another file in the source
  • #define: define a symbol to be replaced during preprocessing
  • #undef: un-define a previously defined symbol
  • #if, #ifdef, #ifndef, #else, #endif: tell the preprocessor to conditionally compile parts of the code
  • #pragma: give a special instruction to the compiler
  • #error: specify a user-defined error
  • #warning: specifiy a user-define warning

When the preprocessor encounters the #include directive, it replaces the #include line with the contents of the included file. Local files are included using quotation marks while system files are included using less/greater than symbols.

#include <stdlib.h>//This is a system file
#include "local.h" //This is a local include file

#define and #undef

When the preprocessor encounters the #define directive, it replaces the symbol with the specified definition. As a matter of convention, defined symbols are written in all capital letters using underscores to separate words. The #undef directive can be used to remove the definition of a previous #define. The following program shows the basic usage of #define.

#include <stdio.h>
#include <stdlib.h>
#define INIT_X_VALUE 10
#define INIT_Y_VALUE 100
int main(int argc, char * argv[]){
     int x, y;
     x = INIT_X_VALUE;
     y = INIT_Y_VALUE;
     printf("X is %d, Y is %d\n", x, y);
     exit(0);
}

#if, #ifdef, #ifndef, #else, #endif

The conditional directives tell the compiler to omit or include certain code snippets based on various criteria. Let’s start with a program example.

#include <stdio.h>
#include <stdlib.h>
#define DEBUG 1
#ifdef DEBUG
#define debug_printf(...) printf(__VA_ARGS)
#else
#define debug_printf(...)
#endif
int main(int argc, char * argv[]){
     debug_printf("This is a debug message\n");
     exit(0);
}

The preceding program illustrates one way to enable and disable debugging messages in a program. It uses the #ifdef directive to tell the compiler to use printf() if DEBUG is defined and just use an empty statement if it is not.

One standard use of the #ifndef directive is called a header guard. If a single program includes the same header more than once, this can sometimes cause problems because some types may be defined more than one time. A header guard prevents these problems by defining a new symbol and only including the header if the new symbol has not yet been defined. This ensures the preprocessor only includes the file one time.

#ifndef MY_HEADER_FILE_H_
#define MY_HEADER_FILE_H_
typedef int my_int_type;
int my_function_prototype(void);
#endif //end if for MY_HEADER_FILE_H_

#error and #warning

The #error and #warning directives allow for user defined errors and warnings that the compiler picks uThe utility of these directives is in making sure the program is configured correctly if there are limits on what conditions the program may compile. The following program illustrates how these directives may be used.

#include <stdio.h>
#include <stdlib.h>
#define DEBUG 1
#if DEBUG != 0
#define debug_printf(...) printf(__VA_ARGS)
#warning "Debugging is turned on"
#else #define debug_printf(...)
#endif
int main(int argc, char * argv[]){
     debug_printf("This is a debug message\n");
     exit(0);
}

The program above uses a preprocessor directive that causes the compiler to warn the user if debugging is enabled. In other situations, #error might be more appropriate. For example, if the user has defined a value that is not in a valid range.

#pragma

The #pragma directive gives special instructions to the compiler. The #pragma directive is especially useful in embedded C programming and can tell the compiler to allocate a certain variable in RAM or EEPROM. It can also tell the compiler to insert a snippet of assembly language code.

The GNU GCC compiler, which is a popular compiler for various embedded architectures such as ARM and AVR, also uses attributes as an alternative syntax to the #pragma directive.

Take Away

C language compilers always preprocess the code and execute any preprocessor directives during that stage. The most commonly used preprocessor directives (especially among beginners) are #include and #define. #include is simple to use; you just need to remember quotes for local files and less/greater than symbols for system files. #define is handy for code maintenance. If a value is fixed, it should be defined as a macro so that if it needs to be changed, it only needs to be changed in one location. Lastly, if you create your own header file, it is imperative that you use a header guard of the form:

#ifndef NAME_H_
#define NAME_H_`

//your header declarations here

#endif

Chapter 8: Compound Data Types

The C languages defines several types of compound data structures. The structures treat the data differently when stored in memory. The following compound data types exist.

  • struct: data in a struct is allocated contiguously in memory
  • union: data in a union occupy the same memory location
  • enum: this is a list of valid values for the specified type

C also provides two important tools for programmers that allow them to define new data types (using typedef) and check the size of existing types (using sizeof).

struct

Of these types the struct is the most commonly used. In programming design, related data is combined in a struct. The C language, per se, does not define any struct’s. However, the C standard library defines a number of them. A good example of this is struct tm which is the data structure for storing calendar time. The following code segment shows how the structure is defined (part of the time.h header).

struct tm {
    int tm_sec;
    int tm_min;
    int tm_hour;
    int tm_mday;
    int tm_mon;
    int tm_year;
    int tm_wday;
    int tm_yday;
    int tm_isdst;
}

The program below shows how to access (read and write) members of a struct using the period . syntax to access members.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>  //"struct tm" is defined here
int main(int argc, char * argv[]){
     struct tm t; //declare t as a struct tm
     t.tm_sec = 30; //assign 30 to the tm_sec member
     t.tm_min = 5; //assign 5 to the tm_min member
     t.tm_hour = 10; //assign 10 to the tm_hour member
     printf("time is %d:%d:%d\n", t.tm_hour, t.tm_min, t.tm_sec);
     exit(0);
}

This program ouputs:

time is 10:5:30
union

The syntax for union is similar to that of struct. However, the memory allocation for a union is very different. For a struct, each member is assigned a unique location in memory, but all members of a union share the same memory location. The size of the union in memory is at least large enough to hold the largest member.

enum

An enum is a list of values that should be assigned to a variable. An enum variable occupies enough memory to hold the largest value of the enum and has values rather than members (unlike union and struct). Using enum is equivalent to using an integer type large enough to hold all enum values. The C compiler does not ensure that values assigned to enum types are in the list.

Combining Compound Data Types

Compound data types (specifically, unions and structs) can be combined in various ways. There can be unions within structs, vice versa, and almost any other combination. The following code shows a couple of examples.

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
struct point {
     //This structure has location and color data
     int x;
     int y;
     int z;
     union color_union { //The color data can be accesses as elements or one number               struct rgb_struct {
                uint8_t r; //red element
                uint8_t g; //green element
                uint8_t b; //blue element
                uint8_t alpha; //transparency element
          } rgb;
          uint32_t rgba; //rgb and transparency elements
     } color; };

int main(int argc, char * argv[]){
     struct point p0;
     p0.x = 100;
     p0.y = 200;
     p0.z = 500;
     p0.color.rgb.r = 255;
     p0.color.rgb.g = 0;
     p0.color.rgb.b = 0;
     p0.color.rgb.alpha = 8;
     printf("%d,%d,%d %d\n", p0.x, p0.y, p0.z, p0.color.rgba);
     exit(0);
}

In this example, there is a struct within a union within a struct. When p0 is assigned memory, it has the following layout assuming int is four bytes:

    +------------------+-----------------+---------------+
15  |                  |      alpha      |               |
14  |                  |      b          |               |
13  |                  |      g          |               |
12  |       color      |      r          |    rgba       |
    +------------------------------------+---------------+
    |                  |
    |                  |
    |                  |
 8  |       z          |
    +------------------+
    |                  |
    |                  |
    |                  |
 4  |       y          |
    +------------------+
    |                  |
    |                  |
    |                  |
 0  |       x          |
    +------------------+

Type Definitions

Users (C Programmers) can define types using any raw or compound data type as well as previously defined user types. The stdint.h header is a good example of user-defined types using purely raw data types. The code snippet below shows a partial, simplied implemenation of stdint.h on a 32-bit processor.

#ifndef _STDINT_H  //this is the header guard
#define _STDINT_H
typedef unsigned int uint32_t;
typedef signed int int32_t;
typedef unsigned char uint8_t;
typedef signed char int8_t;
typedef unsigned short uint16_t;
typedef signed short int16_t;
#endif

Since this is a header file, the first thing is the header guard as mentioned in the [preprocessor directives lesson]({{< relref “2013-12-08-Preprocessor.md” >}}). Next, the typedef keyword is introduced which defines a new data type than can be declared just like any other data type. The example then uses raw types to define the C99 integers in stdint.h. The code below illustrates the use of these types alongside raw types; it also introduces a new C keyword: sizeof.

#include <stdlib.h>
#include <stdio.h>  
#include <stdint.h
int main(int argc, char * argv[]){
	unsigned char x;
	uint8_t y; //x and y are the same type on most processors
	int w;
	int32_t z; //show the sizes (number of bytes in memory) used by each variable
	printf("sizeof(x) is %d, sizeof(y) is %d\n", sizeof(x), sizeof(y));
	printf("sizeof(w) is %d, sizeof(z) is %d\n", sizeof(w), sizeof(z));
	exit(0);
}

As shown, uint8_t can be used just like unsigned char after the typedef unsigned char uint8_t line which is located in stdint.h. The program has the following output showing that the uint8_t/unsigned char types use one byte in memory while the int32_t/int types use four bytes in memory.

sizeof(x) is 1, sizeof(y) is 1
sizeof(w) is 4, sizeof(z) is 4

The sizeof keyword can operate both on types and variables. The example above uses the variable, for example sizeof(x). But it is also OK to use sizeof(unsigned char). The sizeof keyword is especially useful with structs. For the calendar time struct mentioned above, we use sizeof(struct tm). This notation is used when the struct has not been defined as a type using typedef.

Take Away

Compound data types in C give the programmer a powerful set of tools for organizing data. The C struct is the most common compound data type and organizes data contiguously in memory. A union allows the same place in memory to be treated as different types. An enum defines a list of values but acts more like a macro (see #define) than a compound data type. Users can also create customized types using the typedef keyword. Finally, the sizeof keyword determines the amount of memory used by a variable or type.

Chapter 9: Keyword Reference

This tutorial (part of the embedded C tutorial) is a list of the C keywords for reference.

Data Types

  • char
  • int
  • long
  • float
  • double
  • _Bool
  • void

Data Type Modifiers

  • unsigned
  • const
  • static
  • volatile
  • extern
  • restrict
  • inline
  • auto
  • register
  • _Complex
  • _Imaginary

Compound Data Types

  • struct
  • enum
  • union
  • typedef

Flow Control

  • if
  • else
  • switch
  • case
  • default
  • goto
  • break
  • for
  • do
  • while
  • continue

Utility

  • sizeof

Chapter 10: Order of Operations

The following is a list of the C order of operations from highest precedence to lowest.
Operators within the same group have equal precedence and evaluate left-to-right or right-to-left as indicated.

Left-to-Right

  • ++ post increment
  • -- post decrement
  • () function call
  • [] array subscripting
  • . structure/union member selection
  • -> element selection via pointer

Examples

int x[0];
x[0] = 1;
x[1] = 2;
int * i = x;
int y;
y = *i++ //y is 1 and i points to x[1]
(*i)++ //now x[1] is 3 and i points to x[1]
char x[4]; //4 bytes, one 32-bit word
(uint32_t*)x[0]; //returns a char value 0 to 0xff cast as a pointer
((uint32_t*)x)[0]; //returns an 32-bit word

Right-to-Left

  • ++ pre increment
  • -- pre decrement
  • + unary plus
  • - unary minus
  • ! logical not
  • ~ bitwise not
  • (<type>) type casting
  • * dereference
  • & address of
  • sizeof() sizeof type or variable

Examples

struct abc {
    int a;
    int b;
    short c;
};
struct abc x;
struct abc * y = &x;
&x.b
//is the same as
&(x.b)

&y->b;
//is the same as because -> is stronger than &
&(y->b);

(int*)&x[0];
//this is an error -- could use
((int*)&x)[0];

Left-to-Right

  • * multiplication
  • / division
  • % modulo

Examples

a + b * x + y
//is the same as
a + (b * x) + y

Left-to-Right

  • + addition
  • - subtraction

Examples

c >> a * b + x * y
//is the same as
c >> ((a * b) + (x * y))

a + b * x + y
//is the same as
a + (b * x) + y

Left-to-Right

  • >> bitwise shift right
  • << bitwise shift left

Examples

a + b >> x + y
//is the same as
(a + b) >> (x + y)

Left-to-Right

  • < less than
  • <= less than or equal to
  • > greater than
  • >= greater than or equal to
if( a < b == x > y ){}
//is equivalent to
if( (a < b) == (x > y)){}

Left-to-Right

  • != not equal to
  • == equal to

Left-to-Right

  • & bitwise and
x & 0x2 == 0
//is equivalent to
x & (0x2 == 0) 
//fix it using ()
(x & 0x2) == 0

a + b & 0x02 == x & 0x02 + y
//is the same as -- when in doubt use ()
(a + b) & (0x02 == x) & (0x02 + y)

Left-to-Right

  • ^ bitwise xor

Left-to-Right

  • | bitwise or

Examples

a | b && x | y
//is the same as 
(a | b) && (x | y)

Left-to-Right

  • && logical and

Examples

a == b && x == y
//is the same as 
(a == b) && (x == y)

a && b == x && y
//is the same as 
a && (b == x) && y

a && b || x && y
//is the same as 
(a && b) || (x && y)

a && b | x && y
//is the same as 
a && (b | x) && y

Left-to-Right

  • || logical or

Examples

a || b && x || y
//is the same as 
a || (b && x) || y

Left-to-Right

  • ?: ternary operator
a = x > y ? x : y;
//is the same as
if( x > y ){
    a = x;
} else {
    a = y;
}

a = x > y ? x >>= z : y;
//is the same as
x >>= z;
if( x > y ){
    a = x;
} else {
    a = y;
}

Right-to-Left

  • = assign
  • += assign sum
  • -= assign difference
  • \*= assign product
  • /= assign quotient
  • %= assign remainder
  • <<= assign shift left
  • >>= assign shift right
  • &= assign and
  • ^= assign xor
  • |= assign or

Examples

a = b == c
//is the same as
a = (b == c)

a = b += c
//is the same as (right to left evaluation)
a = (b += c)
//or
b += c;
a = b;

Left-to-Right

  • , comma (evaluates to the value of the second argument)

Examples

//avoid using comma's as an operator :)

Chapter 11: What Next?

Tags:
X

Thanks for Coming!

Subscribe to news and updates