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
.
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, anunsigned
is followed by the datatype and name, which will be interpreted as an unsigned variable. Using only theunsigned
keyword without a specific datatype will correspond to anunsigned int
.Example
unsigned int myUnsignedInt = 10; unsigned mySecondUnsignedInt = 20;
short
andlong
: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 datatypelong long
which follows the rulesizeof(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 anint
, but has typically a size of 1 byte. The total number of bits stored in achar
is stored in the macroCHAR_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
andfloat
data types are very similar. Instead of using the single-precision format fromfloat
, 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 thedouble
keyword instead of thefloat
.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 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 |
---|---|---|---|---|
|
8 |
1 |
\(-2^{7}\) = -128 |
\(2^{7}-1\) = 127 |
|
8 |
1 |
0 |
\(2^{8}-1\) = 255 |
|
16 |
2 |
\(-2^{15}\) = -32’768 |
\(2^{15}-1\) = 32’767 |
|
16 |
2 |
0 |
\(2^{16}-1\) = 65’535 |
|
32 |
4 |
\(-2^{31}\) = -2’147’483’648 |
\(2^{31}-1\) = 2’147’483’647 |
|
32 |
4 |
0 |
\(2^{32}-1\) = 4’294’967’295 |
|
64 |
8 |
\(-2^{63}\) = -9’223’372’036’854’775’808 |
\(2^{63}-1\) = 9’223’372’036’854’775’807 |
|
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
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.
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.
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;
Pointer address access
To access to the stored address, no additional operator is needed.
printf("The stored address is %p"/*(1)!*/, thePointer/*(2)!*/);
Tip
%p
is used to display pointers.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);
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)!*/);
Error
Not allowed
void*
dereferencing.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)!*/
Correct
Allowed to change the value of the referenced variable
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)!*/
Error
Not allowed to change the value of the referenced variable.
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;
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 PointerIf 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 PointerFor 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 PointersThe 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 PointersAll 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
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
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 |
|
|
Binary |
|
|
|
||
|
||
|
||
|
||
Ternary |
|
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 |
|
!= |
not equal to |
is a not equal to b |
|
< |
less than |
is a less than b |
|
> |
greater than |
is a greater than b |
|
<= |
less than or equal to |
is a less or equal to b |
|
>= |
greater than or equal to |
is a greater than or equal to 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
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; }
Important
Needed function prototype for sum here
Warning
Implicit declaration of function ‘sum’
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;
}
Function pointer argument
Function prototype in style of the function pointer (Same return value and function arguments)
Passing function as function pointer
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
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)!
Initialisation of
myArray
Reference to the address of the first element
Access to the first element. Will result in value1 = 1
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
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
Tip
Default initial value 0
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; }
Tip
Creating an enumeration
Tip
Create and initialise a variable.
Tip
Using the enumeration as return value and argument.
Enumeration Quiz¶
Enumeration Quiz
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
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
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)!
Important
The
'\0'
character is set at the end by the compiler.
char str[20] = "C is fun!";//(1)!
Important
The
'\0'
character is set at the end by the compiler.
char str[20] = {'C',' ','i','s',' ','f','u','n','!','\0'};//(1)!
Important
The
'\0'
character must be set by the user.
char str[] = {'C',' ','i','s',' ','f','u','n','!','\0'};//(1)!
Important
The
'\0'
character must be set by the user.
char *str = "C is fun!";//(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)!
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)!
Important
destSize -1
is used to leave space for the end token0
.Important
Functions runs until the
src
string is finished or the maximum number of characters given as the last argument (destSize -1
) is reached.