C Programming Refresh

The high-level programming language C is used to program MCUs. Generally, the programmer of this kit is expected to be familiar with the C programming language. Read here for more about the language skill level needed to implement the kit.

Keywords

The C programming language has several keywords hat can be used to implement the desired functionality. A list of all keywords, including a short description, can be found at cppreference.com or you can look into the C Reference Card.

C Reference Card

cReferenceCarddownloadopen_in_new

Cannot display PDF, please download it with the link above.

Data Types and Size

Each variable in C has an associated data type. It specifies the type of data the variable can store, such as integer, character, floating point number, etc. Therefore, the data type gives the notion of memory size and how the data is encoded in the memory.

Base Data Types

The standard C data types are int, char, float and double.

The integer type – int

int type stores data as “whole numbers”. An integer is typically the size of a machine word. This size depends on your system architecture. It is 32 bits (four bytes) for the most modern computers. A basic integer can store a number between \(-2^{N-1}\) to \(2^{N-1}-1\), where N is the bit number of the integer memory size.

The basic principle is used to create and initialise an integer variable.

Example
int myInitialisedInt = 17;
int myUninitialisedInt;

The encoding of the data can be modified by the following keywords, which change the integer range:

  • unsigned:

    With the keyword unsigned, the range of an integer is modified to represent only positive numbers. This results in doubling the range for positive numbers compared to the signed range. Generally, an unsigned is followed by the datatype and name, which will be interpreted as an unsigned variable. Using only the unsigned keyword without a specific datatype will correspond to an unsigned int.

    Example
    unsigned int myUnsignedInt = 10;
    unsigned mySecondUnsignedInt = 20;
    
  • short and long:

    These keywords are modifiers for the size of an integer, but they also depend on the underlying hardware—the system. So, it is not guaranteed that a short is always smaller than an int and a long is always larger than an int. The rule which is guaranteed is that sizeof(short) <= sizeof(int) <= sizeof(long). Some compiler accepts the datatype long long which follows the rule sizeof(long) <= sizeof(long long).

    The mentioned keywords can also be combined with the unsigned keyword.

    Example
    unsigned short myUnsignedShort;
    short myShort = -42;
    long myLong = 1000000;
    long long myLongLong;
    
  • char:

    A char is the abbreviation for character. It stores the same kind of data as an int, but has typically a size of 1 byte. The total number of bits stored in a char is stored in the macro CHAR_BIT. A char is mostly used to store characters, hence its name. Most implementations use the ASCII character set as the execution character set, but it’s best not to know or care about that unless the actual values are important.

    Basic characters are 'a', 'B', '1', ',' and some special characters like '\n' (newline or LF-Linefeed), '\t' (horizontal tab) or '\0' (Null or end-token).

    Important

    It is important to use the single apostrophes like 'A' to declare it as a character. The double apostrophes are used to declare a c-character array like "This is a C-String". Read more about this topic in the section Characters and C-Strings.

    The characters are represented with a decimal number, e.g. the character 'A' is represented with the decimal number 65. For initialisation, both values are accepted. It is recommended to use the characters for a higher readability than using the decimal or hexadecimal values.

    Example
    char myA = 'A';
    char mySecondA = 65; // Results in character 'A'.
    

Floating Point Numbers

  • float:

    A float is the abbreviation for floating point. It can stores real numbers in an defined precision. float use the single-precision format of a floating point number. The size is typically one machine word.

    A floating point number can be initialised with floating literals which use a f or F as suffix. Additional to a decimal notation, the scientific notation can also be used.

    Example
    float myFloat = 0.0011f;
    float myScientificFloat = 1.1e-3f;
    
  • double:

    The double and float data types are very similar. Instead of using the single-precision format from float, double use, as the name suggests, double-precision format. The size is typically two machine words.

    The distinction between floats and doubles was made because of the different sizes of the two types. When C was first used, space was at a minimum and so the judicious use of a float instead of a double saved some memory. Nowadays, with a lot free memory available, this problem is removed. Sometimes, some C implementations use doubles instead of floats when you declare a float variable.

    The initialisation is the same as for a float, except using the double keyword instead of the float.

    Example
    double myFloat = 0.0011f;
    double myScientificFloat = 1.1e-3f;
    
Tip

If you are interested which data type size your computer use, download, compile and execute the sizeof.cdownload source file.

Preview sizeof.c
/**
 * @file sizeof.c
 * @ref BTS1230
 * @ref MCU Programming
 * @copyright Bern University of Applied Sciences, 2021. All rights reserved.
 *
 * @author Pascal Mainini <pascal.mainini@bfh.ch>
 * @author Adrian STEINER <adrian.steiner@bfh.ch>
 * @date 20.09.2021
 * @date 17.10.2024
 *
 * @brief Print memory size and range for different data types.
 *
 * This program prints the actual space used in memory by different data types
 * using the sizeof-operator.
 *
 * Compile it using:
 * gcc -std=c17 -Wall -Wextra -Wpedantic -O3 -o sizeof sizeof.c
 *
 * Run it using:
 * ./sizeof
 */

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

#define EMPTY_LINE (puts(""))

int main(void) {   
  
  printf("A  char is               %2zu bytes and has a range of %20d to %20d\n", sizeof(char), CHAR_MIN, CHAR_MAX);
  printf("A  short is              %2zu bytes and has a range of %20d to %20d\n", sizeof(short), SHRT_MIN, SHRT_MAX);
  printf("An int is                %2zu bytes and has a range of %20d to %20d\n", sizeof(int), INT_MIN, INT_MAX);
  printf("A  long is               %2zu bytes and has a range of %20ld to %20ld\n", sizeof(long), LONG_MIN, LONG_MAX);
  printf("A  long long is          %2zu bytes and has a range of %20lld to %20lld\n", sizeof(long long), LLONG_MIN, LLONG_MAX);
  EMPTY_LINE;
  printf("An unsigned char is      %2zu bytes and has a range of %d to %21u\n", sizeof(unsigned char), 0, UCHAR_MAX);
  printf("An unsigned short is     %2zu bytes and has a range of %d to %21u\n", sizeof(unsigned short), 0, USHRT_MAX);
  printf("An unsigned int is       %2zu bytes and has a range of %d to %21u\n", sizeof(unsigned int), 0, UINT_MAX);
  printf("An unsigned long is      %2zu bytes and has a range of %d to %21lu\n", sizeof(unsigned long), 0, ULONG_MAX);
  printf("An unsigned long long is %2zu bytes and has a range of %d to %21llu\n", sizeof(unsigned long long), 0, ULLONG_MAX);
  EMPTY_LINE;
  printf("A float is               %2zu bytes and has a range of %.5g   to %.5g\n", sizeof(float), FLT_MIN, FLT_MAX);
  printf("A double is              %2zu bytes and has a range of %.5g  to %.5g\n", sizeof(double), DBL_MIN, DBL_MAX);
  printf("A long double is         %2zu bytes and has a range of %.5Lg to %.5Lg\n", sizeof(long double), LDBL_MIN, LDBL_MAX);
  
  return 0;
}
See also

wikibooks.org/wiki/C_Programming/Variables offers a big overview about the data types in C. Read there more about this topic.

Defined sized Data Types

By including the header file #include <stdint.h> or #include <inttypes.h>, standardised integers with a exact size are defined. This has the advantage of being able to use variables with a defined size independently of the system. This is useful if you write a module for your computer and for your MCU. In MCU programming, it is used to write to registers with a defined size to manipulate all the desired bits, but not to overwrite more. The basic defined sizes of the variables are:

Type

Bits

Bytes

Min Value

Max Value

int8_t

8

1

\(-2^{7}\) = -128

\(2^{7}-1\) = 127

uint8_t

8

1

0

\(2^{8}-1\) = 255

int16_t

16

2

\(-2^{15}\) = -32’768

\(2^{15}-1\) = 32’767

uint16_t

16

2

0

\(2^{16}-1\) = 65’535

int32_t

32

4

\(-2^{31}\) = -2’147’483’648

\(2^{31}-1\) = 2’147’483’647

uint32_t

32

4

0

\(2^{32}-1\) = 4’294’967’295

int64_t

64

8

\(-2^{63}\) = -9’223’372’036’854’775’808

\(2^{63}-1\) = 9’223’372’036’854’775’807

uint64_t

64

8

0

\(2^{64}-1\) = 18’446’744’073’709’551’615

See also

The header file includes also other types for optimising memory size or processing speed. Read more about these special data types like int_leastN_t or int_fastN_t at wikibooks.org/wiki/C_Programming/stdint.h#Minimum-width_integer_types.

Data Types Quiz

Data Types Quiz
# What is the decimal value of the variable *myChar*? ```c int a = 0; char myChar = '0'; ``` > Remember on the ascii table 1. [x] 48 1. [ ] 0 1. [ ] 65 1. [ ] 97 # In which header file are standardised integers defined? Defined data types like `int8_t`, `uint16_t` or `uint32_t`. - [x] <stdint.h> - [x] <inttypes.h> - [ ] <stdio.h> - [ ] <stdlib.h> > There are multiple answers correct # What is the result of the following comparison? ```c sizeof(int) == sizeof(long) ``` 1. [x] Depends on the system 1. [ ] True 1. [ ] False

Pointers

Pointers are one of the core components in the programming language C. The concept of pointers offers a new handling of memory which results in the most cases of less required memory, unnecessary memory copies and faster access.

Definition

All used variables are stored in the memory at a specific address. This address can be stored in a pointer variable. The pointer can now be dereferenced and the data at this address can be modified.

The advantage of this system is that the pointer variable size depends on the system architecture and is the same for all pointers types. They are independent of the size of the referenced variable.

Demo
Pointer explanation

Source: Pointer explanation

Handling

The pointer includes new functionalities to work with variables. As it is recommend to initialise a pointer immediately at the declaration, we combine this two steps together.

Warning

As pointers reference a memory segment, it is recommend to initialise the pointer at the declaration. Dereference an uninitialised pointer can give unreasonable behavior. Initialise it at least with NULL. Read more about this in the subsection NULL Pointers.

  1. Pointer declaration and initialisation

    To get the address of a variable, the address of operator & is used.

    <type> *<prtName> = <initAddress>;
    
    int myInt = 0;
    int *myIntPtr = &myInt;
    
  2. Pointer address access

    To access to the stored address, no additional operator is needed.

    printf("The stored address is %p"/*(1)!*/, thePointer/*(2)!*/);
    
    1. Tip

      %p is used to display pointers.

    2. Tip

      The pointer can be of any datatype. The address will always have the same size.

    int base = 10;
    int *thePointer = &base;
    printf("The address of base is %p", thePointer);
    
  3. Pointer dereferencing

    To get access to the value in the stored address, the dereference operator * is used.

    printf("The value of thePointer's address is %d", *thePointer);
    
    int base = 10;
    int *thePointer = &base;
    printf("The value of base is %d", *thePointer);
    

Pointer types

With pointers, several concepts can be connected with them to offer new functionalities.

NULL Pointers

The Null Pointers are those pointers that do not point to any memory location. They can be created by assigning a NULL value to the pointer. A pointer of any type can be assigned the NULL value.

Example
int *myIntPtr = NULL;
void *myVoidPtr = NULL;

It is recommended to initialize all pointers to NULL rather than leaving them uninitialized. A good practice is also to assign NULL to unused pointers to be sure the referenced memory segment is still active.

See also

Get more examples and descriptions at NULL Pointer in C.

Structure Pointers

Pointers to Structs are helpful to handle in functions or to save memory. Read more about this type of pointer in References to Structures.

Function pointers

Functions is the basic building block of a C program. Instead of pointing to data with a basic pointer, a function pointer points to functions which results to reference implemented code. Read more about this type of pointer in the subsection References to Functions.

Double Pointers

A pointer can reference another pointer, which is called double pointer or pointer-to-pointer.

Example
int myVar = 10;
int *ptr = &myVar;
int **doublePtr = &ptr;

This concept is used for Multidimensional Arrays, data structures, string arrays or function arguments. The double pointer can be extended to int ***triplePtr, int ****quadruplePtr and so on. This is not recommend because it will be very complex to understand and is error-prone.

See also

Reade more about double pointer with more code snippets and a deeper description.

Void Pointers

A void * is a pointer that has no associated data type. It can not be directly dereferenced. It must be casted to the corresponding data type to be dereferenced.

Example
int val = 10;
void *ref = (void*) &val;
printf("Value: %d", *ref/*(1)!*/);
printf("Value: %d", *(int*)ref/*(2)!*/);
  1. Error

    Not allowed void* dereferencing.

  2. Correct

    Correct dereferencing with casting to an int*.

A void pointer is mostly used as a reference to not specify a data type, e.g.:

  • Data structures to handle any data

  • Return value of dynamic allocated memory

  • Function arguments of standard functions to handle any data type like memcpy(), memset(), qsort() and many others

See also

Read more about void pointers with more examples and code snippets.

Const Pointers

A pointer can be used as a read only variable, as a read-only location reference or both together:

  • Const pointer

In a constant pointer, the memory address cannot be modified once is defined. This can be used to point always to the same variable like a user data reference.

Example
int valA = 10;
int valB = 5;
int * const constPtr = &valA;

(*constPtr)++; /*(1)!*/
constPtr = &valB; /*(2)!*/
  1. Correct

    Allowed to change the value of the referenced variable

  2. Error

    Not allowed to change the reference.

  • Pointer to const

The pointer to a constant allows to change the referenced address of the pointer, but not to change the value of the referenced variable. This can be used to reference constant values like default values or get the values from a const array.

Example
int valA = 10;
int valB = 5;
const int *ptrToConst = &valA;

(*ptrToConst)++; /*(1)!*/
ptrToConst = &valB; /*(2)!*/
  1. Error

    Not allowed to change the value of the referenced variable.

  2. Correct

    Allowed to set a new reference.

  • Both together

A mix of both systems is also allowed. This can be used to pass to a function a constant variable per reference.

Example
int valA = 10;
int valB = 5;
const int* const constPtrToConst = &valA;

/*(1)! */
(*constPtrToConst)++;
constPtrToConst = &valB;
  1. Error

    Not allowed to change something of this pointer. The entire pointer is read only.

Pointer Arithmetics

A small set of valid operations are allowed for modifying pointers.

  • Increment (++) and decrement (--) of a Pointer

    If you increment or decrement a pointer, the address will be changed with the memory size of the defined pointer data type. For Void Pointers, the size will be 1 byte.

  • Addition (+) and subtraction (-) of integers to a Pointer

    For the basic addition and subtraction, the same principle is applied as for the incrementation. The integer number of the addition or subtraction gets multiplied with the data type size.

  • Difference (aPtr - bPtr) of two Pointers

    The subtraction between two pointer is allowed and will return the number of elements between them. The elements have the size of the used datatype of the pointer.

  • Comparison (<>!=) of two Pointers

    All Relational operators can be used with pointers. The data type of the pointer does not matter. Compared is the referenced address.

Example
uint32_t myVar[] = {1, 2};
uint32_t *myPtr = myVar;
myPtr++;
printf("Address before: %p, after increment: %p\n", (void*)myVar, (void*) myPtr);
printf("Difference in increments: %ld\n", myPtr - myVar);
printf("Difference in bytes: %ld\n", (uint8_t*)myPtr - (uint8_t*) myVar);
printf("Stored same address? %s\n", ((double *)myPtr == (void *)myVar) ? "true" : "false");

Pointers Quiz

Pointers Quiz
# How do you initialise the int pointer myPtr with NULL? 1. int 1. * 1. myPtr 1. = 1. NULL 1. ; # Which operation is allowed for a `uint8_t *`? ```c uint8_t arr[] = {17, 42}; uint8_t *ptr = arr; ptr++; // Operation A (*ptr)++; // Operation B ``` 1. [ ] A 1. [ ] B 1. [x] Both of them 1. [ ] None of them # Which operation is allowed for a `const uint8_t *`? ```c uint8_t arr[] = {17, 42}; const uint8_t *ptr = arr; ptr++; // Operation A (*ptr)++; // Operation B ``` 1. [x] A 1. [ ] B 1. [ ] Both of them 1. [ ] None of them # Which operation is allowed for a `uint8_t * const`? ```c uint8_t arr[] = {17, 42}; uint8_t * const ptr = arr; ptr++; // Operation A (*ptr)++; // Operation B ``` 1. [ ] A 1. [x] B 1. [ ] Both of them 1. [ ] None of them # Which operation is allowed for a `const uint8_t * const`? ```c uint8_t arr[] = {17, 42}; const uint8_t * const ptr = arr; ptr++; // Operation A (*ptr)++; // Operation B ``` 1. [ ] A 1. [ ] B 1. [ ] Both of them 1. [x] None of them # What is the result of the followed swap function? ```c void swap(void *p, void *q){ void * tmp = p; (*p) = (*q); (*q) = (*p); } ``` 1. [x] Compile error 1. [ ] Runtime error 1. [ ] Swapped the data of p and q # On which address points the following pointer? ```c uint32_t *base = (uint32_t *) 0x64; base+=4; ``` 1. [x] 0x74 1. [ ] 0x68 1. [ ] 0x80 1. [ ] Compiler Error

Global vs. local vs. static variables

The location of the declaration of a variable in C is important. It defines from where to this variable can be accessed and where it will be stored. In C programming, the variables are only accessible inside the region where they are created. This is called scope. A distinction is made between the following scopes.

Global

Variables created outside of a function are called global variable. Global variables are accessible from any scope.

Important

Global variables should only be used as little as necessary. As they are globally accessible, they are less secure and the functions becomes less flexible.

Example
#include <stdio.h>

int x = 17;

void printIncrementedX(void){
    x++;
    printf("X in function is %2d\n", x);
}

int main(void) {
    printIncrementedX();
    x++;
    printf("X in     main is %2d\n", x);
    return 0;
}
See also

Read more about global variables.

Static

Static variables have the property of preserving their value even after they are out of their scope. This results their current value being saved and they will not be reinitialized in the new scope.

Example
#include <stdio.h>

void printIncrementedX(void) {
   static int x = 0;
   x++;
   printf("X in function is %2d\n", x);
}

int main() {
   int x = 10;
   for (; x > 0; x--) {
      printIncrementedX();
      printf("X in     main is %2d\n", x);
   }
   return 0;
}
See also

Read more about static variables.

Local

Local variables are only accessible in their scope and will be deleted with the corresponding scope.

Example
#include <stdio.h>

void printIncrementedX(void) {
   int x = 0;
   x++;
   printf("X in function is %2d\n", x);
}

int main() {
   int x = 10;
   for (; x > 0; x--) {
      printIncrementedX();
      printf("X in     main is %2d\n", x);
   }
   return 0;
}
See also

Read more about local variables.

Scope Quiz

Scope Quiz
# Which variable has the longest scope? ```c int a; int main{ int b; // . // . // . } int c; ``` 1. [x] a 1. [ ] b 1. [ ] c 1. [ ] All the same # What is the output of the following application? ```c #include <stdio.h> int x = 0; void adder(void) { static int x = 10; printf("x = %d, ", ++x); } int main(void) { for (; x < 3; x++) { adder(); printf("x = %d, ", x); } } ``` 1. [x] None of them 1. [ ] x = 11, x = 1, x = 12, x = 2, x = 13, x = 3, 1. [ ] x = 11, x = 1, x = 11, x = 2, x = 11, x = 3, 1. [ ] x = 1, x = 2, x = 3, x = 4, x = 5, x = 6, 1. [ ] x = 11, x = 12, x = 11, x = 12, x = 11, x = 12, 1. [ ] Compile Error # What is the return value of the following decider function? ```c int decider(void) { int x = 0; if(0 == x) { int x = 10; } else { int x = 20; } return x; } ``` 1. [x] 0 1. [ ] 10 1. [ ] 20 1. [ ] Compile error

Operators

Operators are mathematical and logical operands to modify or compare a variable. C knows three groups of operators, which can be divided into different types.

Operators at a glance

Group

Operators

Type

Unary

++ --

Unary Operators

Binary

+ - * / %

Arithmetic Operators

< <= > >= == !=

Relational Operators

&& || %

Logical Operators

& | << >> ~ ^

Bitwise Operators

=

+= -= *= /= %=

&= |= <<= >>= ~= ^=

Assignment Operators

Ternary

?:

Ternary Operator

Unary Operators

Unary operators relate their calculation on a single operand. Part of this type are the increment (++) and decrement (–) operators. The position of the operators plays a decisive role in determining when the incrementation takes place.

  • Prefix operation When the operant is used in prefix mode (i.e. ++x), the operation is performed before the value is read.

    Example
    uint8_t a = 1;    // => a = 1
    uint8_t b = --a;  // => a = 0, b = 0
    
  • Postfix operation When the operant is used in postfix mode (i.e. x--), the operation is performed after the value is read.

    Example
    uint8_t a = 1;    // => a = 1
    uint8_t b = a--;  // => b = 1, a = 0
    
Warning

In most cases, there is no significant performance difference, but the prefix operation can be slightly more efficient in certain contexts (e.g., in C++) as it doesn’t store the original value. When the value is effectively changed in memory depends on the compiler. For this reason, using prefixed increment/decrement may prevent undesired side effects and help to avoid bugs.

Note

In for loops in the execution expression it doesn’t care which method is used.

Arithmetic Operators

Arithmetical operators includes the elementary arithmetic math operations and the modulo operator.

Operator

Name

Arithmetic operation

Syntax

+

Addition

Add two operands

x+y

-

Subtraction

Subtract the second operand from the first

x-y

*

Multiplication

Multiply two operands.

x*y

/

Division

Divide the first operand by the second operand

x/y

%

Modulus

Calculate the remainder of the division between the first operand divided by the second

x%y

Relational Operators

Relational operators are used to compare two variables and produce a single boolean output.

Operator

Operator name

Description

Example

==

equal to

is a equal to b

a == b

!=

not equal to

is a not equal to b

a != b

<

less than

is a less than b

a < b

>

greater than

is a greater than b

a > b

<=

less than or equal to

is a less or equal to b

a <= b

>=

greater than or equal to

is a greater than or equal to b

a >= b

Logical Operators

These operators are used to combine two or more boolean expressions to perform a logical operations and produce a single boolean output.

  • AND: &&

x

y

x && y

0

0

0

0

1

0

1

0

0

1

1

1

Syntax: x && y

  • NOT: !

x

!x

0

1

1

0

Syntax: !x

  • OR: ||

x

y

x || y

0

0

0

0

1

1

1

0

1

1

1

1

Syntax: x || y

Bitwise Operators

These operators are used to perform bit-level operations on data.

  • Bitwise AND &

Bitwise AND operation on every bit of the variables.

Example
uint8_t a = 0x14;  // = 0b00010100 = 20
uint8_t b = 0x0F;  // = 0b00001111 = 15
uint8_t c = a & b; // = 0b00000100 =  4
  • Bitwise OR |

Bitwise OR operation on every bit of the variables.

Example
uint8_t a = 0x14;  // = 0b00010100 = 20
uint8_t b = 0x0F;  // = 0b00001111 = 15
uint8_t c = a | b; // = 0b00011111 = 37
  • Bitwise XOR ^

Bitwise eXclusive OR operation on every bit of the variable.

Example
uint8_t a = 0x14;  // = 0b00010100 = 20
uint8_t b = 0x0F;  // = 0b00001111 = 15
uint8_t c = a ^ b; // = 0b00011011 = 33
  • Bitwise NOT ~

Toggles every bit in the variable.

Example
uint8_t a = 0x14; // = 0b00010100 =  20
uint8_t b = ~a;   // = 0b11101011 = 235
  • Bitwise Left Shift <<

Shifts the value to the left and fills up with 0. The surplus bits on the left side are cut off.

Example
uint8_t a = 0x14;   // = 0b00010100 =  20
uint8_t b = a << 3; // = 0b10100000 = 160
  • Bitwise Right Shift >>

Shifts the value to the right and fills up with 0. The surplus bits on the right side are cut off.

Example
uint8_t a = 0x14;   // = 0b00010100 = 20
uint8_t b = a >> 3; // = 0b00000010 =  2

Assignment Operators

All Arithmetic operators and Bitwise operators can directly be used with the assign operator =. The system to write is always to write the right value to left target.

Important
<target> = <value>;
a = 10;

This shortcut reduce the programming effort and is more efficient. It is mostly used with literal constants.

Example
uint8_t a = 10; // a = 10
a += 10;        // a = 20
a |= 0x01;      // a = 0x14 | 0x01 = 0x15

Ternary Operator

THe ternary or conditional operator is a special operator which takes three operands. It is a short-hand if-else condition to reduce multiple coding lines in one for simple conditions. For complex or longer conditions, it is recommend to use the basic if-else condition.

Demo
variable = (condition) ? expressionTrue :  expressionFalse;
uint8_t a = 10;
uint8_t b = 15;
uint8_t max = (a > b) ? a : b;

Operators Quiz

Operators Quiz
--- shuffle_answers: false --- # What is the result of following calculation? ```c 10 % 3 ``` 1. [ ] 0 1. [x] 1 1. [ ] 2 1. [ ] 3 # What is the output of the following expression? ```c bool a = true; bool b = false; if(a && !b){ puts("true"); } else { puts("false"); } ``` 1. [x] true 1. [ ] false # What is the hexadecimal result of following calculation? ```c uint8_t a = 0x11 & 0x10; ``` 1. [ ] 0x01 1. [x] 0x10 1. [ ] 0x11 1. [ ] 0x21 # What is the hexadecimal value of the variable a? ```c uint8_t a = 0x1 | (1<<6); ``` 1. [ ] 0x00 1. [ ] 0x21 1. [x] 0x41 1. [ ] 0x81

Functions

A function in C is a set of statements that, when called, perform some certain tasks. It is the basic building block of a C program that provides modularity and code reusability. The programming statements of a function are enclosed within braces { }, having certain meanings and performing certain operations. They are also called subroutines or procedures in other languages.

Declaration (prototype) and definition (implementation)

Function prototypes defines the return type, the name and the arguments of a function. In the C language, each basic function requires a unique name and cannot be overloaded as in C++.

Demo
returnType functionName(type argument1, type argument2, ...);
int diff(int minuend, int subtrahend);

The location of the prototype is important that the function is recognised by other functions. The prototype must be defined before using it in other functions. Generally they are defined at the beginning of the source file for private functions or in the header file for public functions.

Example
#include <stdio.h>

int diff(int minuend, int subtrahend);
//(1)! int sum(int a, int b);

int main(void) {
  int a = 10;
  int b = 6;
  int diffRes = diff(a, b);
  printf("%d - %d = %d\n", a, b, diffRes);
  int sumRes = sum(a, b); //(2)!
  printf("%d + %d = %d\n", a, b, sumRes);
}

int sum(int a, int b); //(3)!

int diff(int minuend, int subtrahend) { return minuend - subtrahend; }

int sum(int a, int b) { return a + b; }
  1. Important

    Needed function prototype for sum here

  2. Warning

    Implicit declaration of function ‘sum’

  3. Error

    Wrong place of function declaration

Passing arguments

Information can be passed to functions as a parameter. Parameters act as variables inside the function. Parameters are specified after the function name, inside the parentheses. You can add as many parameters as you want, just separate them with a comma.

In C, all arguments are copied to the function. By using pointers, only the address of the argument is copied. This results in two basic methods to pass a variable into the function:

  • Call by value:

Call by value in C is the argument is set with a value and this value can be used in function for performing the operation. Values passed in the function are stored in the function memory so the changes performed in the function do not affect the actual value of the variable passed.

Example
#include <stdio.h>

// Call by value example
int sum(int x, int y) {
  return x + y;
}

int main() {
  int a = 3, b = 2;
  // Function Called
  int r = sum(a, b);
  printf("Sum of %d and %d = %d\n", a, b, r);

  return 0;
}
  • Call by reference:

Call by reference is the method in C where the function is called with passing a memory address as an argument. The address is passed as a pointer. A change performed in the value inside the function is also performed to the value of the variable outside the function. With this method, not only one value can be returned to the user. Another advantage is the effort of the CPU to only copy the address instead of copy the hole object. For this reason, this method is generally used for Structs. For more safety to not modify anything of the reference, remember on the section Const Pointers.

Example
#include <stdio.h>

// Call by reference
void swap(int *x, int *y) {
  int temp = *x;
  *x = *y;
  *y = temp;
}

int main() {
  // Declaring Integer
  int x = 1, y = 5;
  printf("Before swap: x:%d, y:%d\n", x, y);
  // Calling the function
  swap(&x, &y);
  printf("After swap:  x:%d, y:%d\n", x, y);
  return 0;
}

Return types

The return value can be used as the result of the function, as a function state or without any return value, represented with void. A good practice is when returning the result of the function, a calculated impossible value is used as a failure value. Using an enumeration with a Typedef to make the states more readable is a good practice.

See also

For more information about C functions, read geeksforgeeks.org/c-functions/.

References to Functions

Any function in a program also has a memory address. Instead of pointing to data, references to functions point to implemented code. This results in storing the starting address of executable code. The user-friendliness of a function pointer consists of offering a high level of abstraction with a high degree of flexibility for the user.

  • Declaration

To reference to a function, the function pointer must be created. The pointer needs the return type, a name and the arguments types:

Demo
returnType (*functionPointerName)(argumentType, argumentType...);
int (*myFunction_ptr)(int, double, char);
  • Initialisation

The function pointer is initialised with a function of the same format (same return type and arguments).

Demo
returnType myFunctionName(type1 argName1, type2 argName2)
returnType (*functionPointerName)(type1, type2);
functionPointerName = myFunctionName;
int myFunction(int number, double real, char character);
int (*myFunction_ptr)(int, double, char);
myFunction_ptr = myFunction;
  • Calling a function pointer

A function pointer is called like a normal function.

Demo
myFunctionPtrName(argumentType1, argumentType2);
myFunction_ptr(a,b,c);
Example
#include <stdio.h>

int operate(int a, int b, char *operationName, int (*operation)(int, int) /*(1)!*/);

int sum(int a, int b); //(2)!
int diff(int minuend, int subtrahend);

int main(void) {
  int a = 10;
  int b = 6;
  operate(a, b, "sum", sum /*(3)!*/);
  operate(a, b, "difference", diff);
  return 0;
}

int operate(int a, int b, char *operationName, int (*operation)(int, int)) {
  int res = operation(a, b);/*(4)!*/
  printf("Operation %s with numbers: %d and %d = %d\n", operationName, a, b, res);
  return res;
}

int sum(int a, int b) {
  return a + b;
}

int diff(int minuend, int subtrahend) {
  return minuend - subtrahend;
}
  1. Function pointer argument

  2. Function prototype in style of the function pointer (Same return value and function arguments)

  3. Passing function as function pointer

  4. Calling the passed function as a normal function

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

// Comparison function
int compare(const void *a, const void *b) {
  return (*(int *)a - *(int *)b);
}

void printArray(int theArray[], size_t n) {
  for (size_t i = 0; i < n; ++i) {
    printf("%2d ", theArray[i]);
  }
}

#define NEW_LINE (puts(""))

int main() {
  int arr[] = {10, 5, 4, 6, 9};
  int n = sizeof(arr) / sizeof(arr[0]);
  printf("Following the input array:  ");
  printArray(arr, n);
  NEW_LINE;

  qsort(arr, n, sizeof(arr[0]), compare);

  printf("Following the sorted array: ");
  printArray(arr, n);
  NEW_LINE;

  return 0;
}
See also

For more examples have a look in geeksforgeeks.org/function-pointer-in-c.

Functions Quiz

Functions Quiz
# Is the following function prototype correctly? ```c int diff(int minuend, int subtrahend); ``` 1. [x] Yes 1. [ ] No, wrong brackets are used 1. [ ] No, there are to much function arguments # How do you create a function with no return value? ```c <returnValue> myFunction(void); ``` 1. [x] void 1. [ ] int 1. [ ] Leave empty 1. [ ] void* # How you would create and initialise a function pointer with the name 'constructor' for the following function? ```c void *data_create(char * name, int date, float size); ``` - [x] ``` void * (*constructor)(char*, int, float) = data_create; ``` - [x] ``` void * (*constructor)(char*, int, float) = &data_create; ``` - [ ] ``` void (*constructor)(char*, int, float) = data_create; ``` - [ ] ``` void *constructor(char*, int, float) = &data_create; ``` - [ ] ``` void * (*constructor)(char, float, int) = data_create; ``` > There are multiple answers correct # How do you use the following function with the defined variables correctly? It is expected that the first argument is the array and the second a reference to an integer value. ```c void myFunc(int *myArray, int *myRef); int arr[3] = 20; int val = 10; ``` 1. [ ] myFunc(arr, val); 1. [x] myFunc(arr, &val); 1. [ ] myFunc(&arr, val); 1. [ ] myFunc(&arr, &val);

Arrays

An array in C is one of the most used data structures. It is a simple and fast way of storing multiple values under a single name.

Array definition

An array in C is a fixed-size collection of similar data items stored in contiguous memory locations. It can be used to store the collection of primitive data types such as int, char, float, etc. and also derived or user-defined data types such as pointers, structures, etc.

To create an array, square brackets [ ] are used after the name and the braces { } are used to initialise the array. There are different possibilities to create an array:

Demo

Template

type arrayName[<sizeDeclaration>] = {<initValue>};
int arrayName[3];
int arrayName[3] = {0};     // Initialise ALL elements with 0
int arrayName[3] = {1};     // Initialise the FIRST element with 1
int arrayName[3] = {1,2,3}; // Initialise the array with 1, 2 and 3
int arrayName[] = {1,2,3}; // Creates an array with a size of 3 with values 1,2,3

Array access

Accessing the array by the name results in receiving the address of the first element of the array. To get access to an element, the array subscript operator [ ] is needed with the desired index. The first element has the index 0.

Example
int myArray[3] = {1,2,3};//(1)!
int * arrayRef = myArray;//(2)!
int value1 = myArray[0];//(3)!
int value2 = myArray[1];
int value2 = myArray[2];
int value3 = myArray[3];//(4)!
  1. Initialisation of myArray

  2. Reference to the address of the first element

  3. Access to the first element. Will result in value1 = 1

  4. Error

    Array access out of boundary! Will result in an undefined value and a bug in your application. The compiler will not give any error or warning.

Warning

Array access out of the array size will result in an undefined behaviour and will not be recognised by the compiler.

See also

A complete summary about arrays can be fount at www.geeksforgeeks.org/c-arrays/.

Multidimensional Arrays

Arrays are not limited as a line, they can be used as two dimensional areas, three dimensional rectangular cuboids and so on. The initialisation and access use the same principle as for one dimensional arrays.

Example
int my2dArray[2][3] = {{1,2,3},{11,12,13}};
int firstElement = my2dArray[0][0];
See also

Get more information and example codes about multi dimensional arrays.

Note

The advantage of multi dimensional arrays are the logic separated access of the elements. The problem lies in the readability of the elements and expanding to additional data requires in the handling a lot of effort. Sometimes a more readable method is to use a one dimensional array of a struct. By naming the members this solution becomes more readable, but it increases in some cases the memory consumption due to padding and the access time by the CPU. What is used is up to the user, it depends on the efficiency, readability, expandability and memory size.

Array Quiz

Array Quiz
# What is the correct order to create an integer array of 20 elements initialised with 0 with the name myArray? > Use the correct brackets for the array and the initialisation 1. int 1. myArray 1. [ 1. 20 1. ] 1. = 1. { 1. 0 1. } 1. ; # Is the following access allowed? ```c int myArray[] = {1,2,3}; int myValue = myArray[3]; ``` 1. [x] No, array access out of boundary! 1. [ ] No, wrong brackets used to access the array > There are the correct brackets used 1. [ ] No, the array is not correct created > The array is correct created. The size must not be given in the square brackets. 1. [ ] Yes, the returned value will be 3 # What is the index of the element with value 3? ```c int myArray[] = {4,8,9,10,3,8,7}; ``` 1. [x] 4 1. [ ] 5 1. [ ] 6 1. [ ] The array is not correct created

Enumerations

Enumerations are used for naming signed int values, similar to constants. The keyword enum results in followed syntax:

Example
enum [name]{ENUMERATOR_1, ENUMERATOR_2, ... , ENUMERATOR_n};

An enumerator is either only a name or a name with a given value. If no value is given, the value of the previous enumerator + 1 is taken (starting at 0):

Example
enum colors{RED, GREEN, BLUE, YELLOW};
// RED=>0(1), GREEN=>1, BLUE=>2 , YELLOW=>3
enum power{OFF, ON, CLOSED = 0/*(2)!*/, OPENED};
// OFF=>0, ON=>1, CLOSED=>0, OPENED=1
  1. Tip

    Default initial value 0

  2. Tip

    Forced assigning of a value.

Important
  • Enumerations are valid for the scope of their definition.

  • Support for values outside of the int range is compiler specific.

  • Multiple enumerators may have the same value.

  • Can be used as a data type for variables, constants, function arguments or return values:

    Example
    enum someValues{A= 0, B, C, MAX_VALUE};//(1)!
    enum someValues theValue = A;//(2)!
    enum someValues incrementState(enum someValues state){//(3)!
       return (state+1)%MAX_VALUE;
    }
    
    1. Tip

      Creating an enumeration

    2. Tip

      Create and initialise a variable.

    3. Tip

      Using the enumeration as return value and argument.

Enumeration Quiz

Enumeration Quiz
# Sort the correct enumeration definition order for the name quiz with the members A and B 1. enum 1. quiz 1. { 1. A, B 1. } 1. ; # Which value has the 'C' constant? ```c enum quiz{A, B=5, C, D} ``` 1. [x] 6 1. [ ] 3 1. [ ] 1 1. [ ] 2

Structs

Structures or short called structs are a way to group several variables into one object. Each variable in the structure is called a member of the structure. Unlike an array, a structure can contain many different data types (int, float, char, etc.).

Structure declaration

You can create a structure by using the struct keyword and declare each of its members inside braces {}. A structure can be defined with a name or anonymously, but the anonymous structure requires an instance.

Example
struct [name] {
   type_x member_1;
   type_y member_2;
   // ...
   type_z member_N;
} [instances];

Structure access

An object of the structure is called instance. The instance can be created like an normal variable or directly in the structure definition. To get access of the member, the structure member operator dot . is used. The initialisation can be summarised in braces {} accessed with the structure member operator or member by member with the same operator.

Example
struct coordinates{
      int x;
      int y;
};

struct coordinates startPoint = {
   .x = 1,
   .y = 2
};

struct coordinates endPoint;
endPoint.x = 1;
endPoint.y = 2;

References to Structures

A pointer can reference a structure. This is mostly used for function arguments which use a structure, because it is in the most cases faster to copy the address instead the entire structure. To access to an element, the basic pointer method can be used by using the base principles of both types with the indirection operator (*) and the dot operator (.), but it gives a much better method with the membership operator (->).

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

struct coordinates {
  double x;
  double y;
};

double distance(const struct coordinates *const start,const struct coordinates *const end) {
    double diffX = end->x - (*start).x;
    double diffY = (*end).y - start->y;
    return sqrt(pow(diffX, 2) + pow(diffY, 2));
}

int main(void) {

  struct coordinates startPoint = {.x = 0.0f, .y = 1.5f};

  struct coordinates endPoint;
  endPoint.x = 3.0f;
  endPoint.y = 5.5f;

  printf("Distance is %.3f\n", distance(&startPoint, &endPoint));

  return 0;
}
See also

Get on the page unstop.com/blog/structure-pointer-in-c information.

Structures Quiz

Structures Quiz
# What is here defined? ```c struct { int x; int y; int z; } coordinates; ``` 1. [x] An anonymous structure with an instance named coordinates 1. [ ] A structure prototype named coordinates 1. [ ] An array with 3 elements 1. [ ] A data type called coordinates # How can you access to the member x in the function? ```c struct coordinates{ int x; int y; int z; }; void coordinates_init(struct coordinates *instance){ /*SOLUTION*/ = 10; } ``` > There are multiple answers correct - [x] instance->x - [x] (*instance).x - [ ] instance.x - [ ] (*instance)->x - [ ] instance[x] # Will this code snippet compile? ```c struct coordinates{ int x;/*1*/ int y; int z; }; struct coordinates a = { .x = 1;/*2*/ .y = 2; };/*3*/ ``` 1. [x] No, the initialisation of the variable `a` at position 2 is comma separated 1. [ ] No, the declaration of the struct members at position 1 is comma separated 1. [ ] No, the semicolon at position 3 is not necessary 1. [ ] Yes, no error and warnings # Are for structure members different data types allowed? For example: ```c struct contact{ char *firstName; char *familyName; int phoneNumber; float size; }; ``` 1. [x] Yes 1. [ ] No

Typedef

To set an alternate name to an existing datatype, the keyword typedef is used with followed syntax:

typedef TYPE ALIAS_NAME;

This feature can be used to define own structure or enumeration types. By using the new type, there is no need to repeatedly write struct or enum.

Example
typedef struct{
   int a;
}MyStruct;

typedef enum {STATE_OK, STATE_WAITING, STATE_ERROR}myState;

myState example(MyStruct *data);

Typedef Quiz

Typedef Quiz
# How you do create an alias name called myRet for an unsigned int? 1. typedef 1. unsigned 1. int 1. myRet 1. ; # Is the name myStruct_ needed for the structure in the following expression? ```c typedef struct myStruct_{ int a; int b; }MyStruct; ``` 1. [x] No, the name of the typedef is MyStruct and creates an alias for the struct 1. [ ] Yes, this is the name of the typedef 1. [ ] Yes, an anonymous structure is not allowed 1. [ ] No, an instance with the name MyStruct is created # Is the following snippet allowed? ```c typedef struct myStruct_{ int a; int b; }MyStruct; void struct_init(MyStruct *instance); ``` 1. [x] Yes 1. [ ] No

Characters and C-Strings

The C String is stored as an array of characters terminated with the null character '\0'. The Null Character in C is a special character with an ASCII value of 0 (zero). It is not the same as the character '0' which has an ASCII value of 48. The null character in C at the end of the string ensures that functions and libraries designed to work with C strings can correctly identify the string’s length and avoid buffer overflows. This concept must always be applied, otherwise unpredictable behavior may occur.

String Initialisation

A string can be initialised in different ways, but not all methods need the null terminator implemented by the user.

char str[] = "C is fun!";//(1)!
  1. Important

    The '\0' character is set at the end by the compiler.

char str[20] = "C is fun!";//(1)!
  1. Important

    The '\0' character is set at the end by the compiler.

char str[20] = {'C',' ','i','s',' ','f','u','n','!','\0'};//(1)!
  1. Important

    The '\0' character must be set by the user.

char str[] = {'C',' ','i','s',' ','f','u','n','!','\0'};//(1)!
  1. Important

    The '\0' character must be set by the user.

char *str = "C is fun!";//(1)!
  1. Important

    The '\0' character is set at the end by the compiler.

C String Handling

Because a C string is fundamentally an array, copying, extending, or modifying it requires more effort than just applying the basic arithmetics as in other programming languages. A C standard library included with #include <string.h> offers many helpful functions to handle this functionality for C strings. A list of all functions including a description and an example can be found at cplusplus.com/reference/cstring/.

Tip

Some functions are delivered in two versions. Use the version with an n in the function name. The n stands for an additional argument where the maximum number of elements can be specified to prevent the function to an array overflow. The basic function is not save to an overflow of your array.

strcpy vs. strncpy
#define destSize (40)
char src[]="Sample string";
char dest[destSize];
strcpy(dest, src);//(1)!
  1. Important

    No check of an overflow if strlen(src) > (destSize-1)

#define destSize (40)
char src[]="Sample string";
char dest[destSize];
strncpy(dest, src, destSize-1/*(1)!*/);//(2)!
  1. Important

    destSize -1 is used to leave space for the end token 0.

  2. Important

    Functions runs until the src string is finished or the maximum number of characters given as the last argument (destSize -1) is reached.

Strings Quiz

Strings Quiz
# How many elements are stored in the array myString? ```c char myString[] = "Hello"; ``` 1. [x] 6 1. [ ] 5 1. [ ] Error, missing null terminator 1. [ ] System dependent # What will be the output of this code snippet? ```c char str[] = {'C',' ','i','s',' ','f','u','n','!'}; puts(str); ``` 1. [x] Unpredictable behavior as the string is not correct initialised 1. [ ] C is fun! 1. [ ] Compile Error # How would you copy a string with the followed initial situation? ```c char baseString[20] = "This is a string!"; char copyString[15]; ``` 1. [x] ```strncpy(copyString, baseString, 15);``` 1. [ ] ```strcpy(copyString, baseString);``` 1. [ ] ```copyString = baseString``` 1. [ ] ```strcpy(baseString, copyString)```