c-language
c-language
Jussi Pohjolainen
C: Mother of all Languages
• Many programming languages are directly derived from C or are heavily influenced by its
syntax and design
• C++:
• Built on C with object-oriented features.
• Objective-C:
• A C-based language with object-oriented extensions.
• Java and C#:
• Adopted C-like syntax and many concepts.
• JavaScript, Go, Rust, Swift, PHP, ...
• Designed in the 70s!
• 1978: Kernighan + Ritchie: "The C Programming Language"
• From 1989 -> 2018 ANSI C standard.
• 2018: C18 (ISO/IEC 9899:2018) language standard
• Free doc: https://en.cppreference.com/w/c
Use Cases?
• Operating Systems:
• C is frequently used to develop operating systems due to its low-level capabilities and
direct access to system hardware. Examples include UNIX, Linux, Windows, and macOS
kernels.
• Embedded Systems:
• C's ability to directly interact with hardware makes it ideal for programming
microcontrollers and embedded systems found in appliances, automotive systems, and
consumer electronics.
• Compilers and Interpreters:
• Many compilers and interpreters for other programming languages are themselves
written in C due to its performance and ability to manipulate system resources
effectively.
• Game Development, Networking, IoT Devices, Libraries...
Machine Code
• Definition: The lowest-level programming language, consisting of
binary code (0s and 1s) that the CPU directly executes.
• Human Readability: Not human-readable; it's difficult to interpret
and write directly.
• Portability: Not portable across different types of CPUs, as
different CPUs have different instruction sets.
• Usage: Used for critical performance-sensitive applications, but
typically generated automatically by compilers.
Assembly Code
• Definition: A low-level programming language that provides a
symbolic representation of machine code instructions.
• Human Readability: Slightly human-readable with mnemonics
for operations (e.g., MOV, ADD) and labels for memory addresses.
• Portability: Hardware-specific and not portable across different
CPU architectures.
• Usage: Used for system programming, device drivers, and
performance-critical code where hardware control is necessary.
• First C – compiler was written with assembly
Assembly:
x86
processor
Assembly:
ARM64
An instruction set
• An instruction set architecture (ISA), is a set of commands that a particular
processor (CPU) can execute
• These instructions are low-level commands that tell the CPU to perform
basic operations
• add, subtract, ..
• AND, OR, XOR
• Data movement
• Control Flow
• Input / Output
• Assembly code is basically using these instruction sets
• x86, ARM, MIPS, PowerPC
Compiling
• Assembly code is compiled into machine code using a tool called
assembler
• NASM (Linux + Windows)
• MASM (Microsoft Macro Assembler)
• FASM (Flast Assembler)
• Translates the assembly instructions into binary machine code
that CPU can execute
Problems with Assembly
• Productivity
• Portability
• Maintenance
• Development tools
C - language
• High – level language
• More focus on programming logic than detailed knowledge of
hardware
• Portability: same code can be compiled to different architectures
• Readability
• Maintainability
// Preprocessor directive to include the standard input-output header file.
// This file contains declarations for the I/O functions like printf and scanf.
#include <stdio.h>
// The printf function is used to print the string "Hello, World!" to the console.
// The \n is a newline character, which moves the cursor to the next line after printing the string.
printf("Hello, World!\n");
// The return statement ends the main function and returns the value 0 to the operating system.
// Returning 0 typically indicates that the program finished successfully.
return 0;
}
Compile and Run
gcc hello.c -o hello
./hello
GCC Compiler
• GCC (GNU Compiler Collection) is a compiler system developed
by the GNU Project
• C, C++, Objective-C, Fortran, Ada, and others
• Open source and free
Installing
• macOS
• Install Xcode command line tools
• Linux (Debian/Ubuntu)
• sudo apt update && sudo apt install gcc
• Windows
• https://winlibs.com/
WinLibs
• Win32 vs. Win64:
• Choose based on whether you need a 32-bit or 64-bit toolchain.
• 64-bit is generally preferred for modern development due to better performance and
the ability to handle larger memory.
• Posix vs MCF
• POSIX Threads
• Standard threading model used in Unix-like systems. Code is portable across systems. Widely
Supported. #include <pthread.h>
• MCF Threads
• Not standardized, less portable, designed for massive concurrency, efficiently handling very
large number of threads.
Variables
Basic Types
• int (32 bits, 4 bytes)*
• Stores integers (whole numbers), both positive and negative.
• float (32 bits, 4 bytes)*
• Stores single precision floating point numbers (numbers with a fractional part).
• double (64 bits, 8 bytes)*
• Stores double precision floating point numbers, providing more precision than float.
• char (8 bits, 1 byte)*
• Stores individual characters or small integers (usually 1 byte).
• * Usually, can vary depending on OS and compiler
Bits, Byte?
bit: 0 or 1
byte (8 bits): 01011010
signed short temperature = -32768; // Allows for negative numbers in a small range
Modifier Types
#include <stdio.h>
#include <math.h>
int main() {
unsigned long x = 18446744073709551615UL;
printf("%lu", x);
return 0;
}
Casting
• Casting in C is a way to convert a variable from one data type to
another.
• This can be done explicitly using casting operators or implicitly by
the compiler
Explicit Casting
• Casting in C is a way to convert a variable from one data type to
another.
• This can be done explicitly using casting operators or implicitly by
the compiler
• Example
int i = 10;
float f = (float) i;
double d = 9.5;
int x = (int) d;
float temp = 65.99;
char ch = (char) temp;
Implicit Casting
• Implicit casting is automatically performed by the compiler when
passing values between different types without explicit cast
• However, care must be taken as implicit casting can sometimes
lead to unexpected results or data loss:
• Example
int i = 42;
float f = i; // We do not lose information
Output and Input
Output
• The printf function in C is a standard output function that is used
extensively for formatting and printing data to the console
• int printf(const char *format, ...);
• format: A format string that includes text to be printed,
placeholders for variables (format specifiers), and formatting
instructions.
• ...: Represents a variable number of arguments that replace the
format specifiers in the format string.
Format Specifiers
Format Specifier Description Example Value Printed Output
%d or %i Integer in decimal base 42 42
%u Unsigned decimal integer 150 150
%f Floating-point number 3.14159 3.141590
Double precision floating
%lf point (used interchangeably 3.1415926535 3.141593
with %f in printf)
Scientific notation
%e or %E 123456.789 1.234568e+05
(lowercase or uppercase)
Shortest representation of
%g or %G %f or %e (lowercase or 0.00012345 1.2345e-04
uppercase)
Unsigned hexadecimal
%x or %X integer (lowercase or 255 ff or FF
uppercase)
%o Unsigned octal integer 10 12
%c Character 65 A
%s String "Hello" Hello
%p Pointer address Address of x 0x7ffeefbff8d8
%% Percent sign N/A %
Escape Sequences
• \n: New line
• \t: Horizontal tab
• \a: Alert (bell) character
• \\: Backslash
• \": Double quote
Examples
printf("Hello, world!\n");
printf("Temperature: %.2f degrees\n", 23.456);
printf("Character: %c\n", 'A');
printf("Hexadecimal: %#x\n", 255);
Input
• The scanf function in C is used to read formatted input from the standard
input, typically the keyboard.
• It is one of the most common input functions in C and serves as a
counterpart to printf, which is used for formatted output.
• int scanf(const char *format, ...);
• format: A format string that contains one or more format specifiers, which
specify the type and format of the data to be read. This string also can
contain literals and whitespace characters, which are used to match the
input.
• ...: The additional arguments must be pointers to variables where the read
values are stored.
Format specifiers
• %d - Reads an integer.
• %f - Reads a float.
• %lf - Reads a double.
• %c - Reads a single character.
• %s - Reads a string until a whitespace is encountered.
• %[...] - Reads a string that matches a set of characters specified
within the brackets.
Examples
int number;
printf("Enter an integer: ");
scanf("%d", &number);
printf("You entered: %d\n", number);
Examples
float pi;
printf("Enter a floating point number: ");
scanf("%f", &pi);
printf("You entered: %f\n", pi);
Examples
char ch;
printf("Enter a character: ");
scanf("%c", &ch);
printf("You entered: %c\n", ch);
Examples
int day, month, year;
printf("Enter day, month, and year: ");
scanf("%d %d %d", &day, &month, &year);
printf("Entered date is: %02d/%02d/%d\n", day, month, year);
Control Flow
If else
int number = 10;
if (number > 0) {
printf("The number is positive.\n");
} else if (number < 0) {
printf("The number is negative.\n");
} else {
printf("The number is zero.\n");
}
switch
char grade = 'B';
switch (grade) {
case 'A':
printf("Excellent!\n");
break;
case 'B':
case 'C':
printf("Well done\n");
break;
case 'D':
printf("You passed\n");
break;
case 'F':
break;
default:
printf("Invalid grade\n");
}
while
int count = 5;
while (count > 0) {
printf("Count = %d\n", count);
count--;
}
for
for (int i = 0; i < 5; i++) {
printf("i = %d\n", i);
}
do while
int a = 5;
do {
printf("a = %d\n", a);
a--;
} while (a > 0);
Conditions
• Conditions within if statements are evaluated as Boolean
expressions where the integer zero (0) represents FALSE
• Any non-zero value is treated as TRUE.
• if(1) { .. }
Quick Start to Memory
Storage Typical Use
Type Location Initialization Lifetime Scope Cases
Limited to the Temporary data
Duration of
Local Stack Not automatic block where within
function call
declared functions
Data needed
Automatic Accessible from
Data Segment Entire program across multiple
Global (zero if any part of the
(.data or .bss) duration functions or
uninitialized) program
modules
void function() {
int stackVar = 10; // Local variable allocated on the stack
printf("Stack variable value: %d\n", stackVar);
}
int main() {
function();
return 0;
}
Pointers and references
#include <stdio.h>
#include <stdlib.h>
int main() {
int a = 5;
// let's change a
*memoryAddressOfA = 6;
return 0;
}
Heap
#include <stdio.h>
#include <stdlib.h>
int main() {
return 0;
}
Memory Leak
#include <stdio.h>
#include <stdlib.h>
int main() {
if(1) {
return 0;
}
Fix
#include <stdio.h>
#include <stdlib.h>
int main() {
int *heapVar = NULL; // Declare heapVar outside the if block
if(1) {
heapVar = malloc(sizeof(int)); // Allocate memory on the heap
printf("Address is %p\n", heapVar);
*heapVar = 20; // Assign value to allocated memory
printf("Value is %d\n", *heapVar);
printf("Heap variable value: %d\n", *heapVar);
}
return 0;
}
Arrays
Arrays
• Arrays in C are fundamental data structures that consist of
elements of the same data type placed contiguously in memory
Array in Stack
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5}; // Array declared on the stack
int i; // Loop variable also on the stack
int main() {
int size = 5;
int *arr = malloc(size * sizeof(int)); // Dynamically allocate memory for the array on the
heap
return 0;
}
Dynamic array in heap
#include <stdio.h>
#include <stdlib.h> // Include for malloc and free functions
int main() {
int size;
int *arr = malloc(size * sizeof(int)); // Dynamically allocate memory for the array on the heap
return 0;
}
Pointers with Arrays
#include <stdio.h>
#include <stdlib.h>
int main() {
// Allocate memory for three integers
int *numbers = malloc(3 * sizeof(int));
return 0;
}
Char Array
Char arrays
• String: char array that ends with '\0'
• Declaration
• char str1[6] = "hello"; // Array size is 6, including the null terminator
• Accessing
• char firstChar = str1[0];
• Output
• printf("%s\n", str1); // Output the string
• Input
• scanf("%s", str2); // Read a string into str2 (unsafe, prefer fgets)
Avoid scanf
• Using scanf to read strings into character arrays can be
problematic due to its potential to cause buffer overflow
• Buffer overflow occurs when the data written to a buffer exceeds
its storage capacity, which can lead to undefined behavior,
program crashes, or security vulnerabilities.
• Example
• char name[10];
• printf("Enter your name: ");
• scanf("%s", name); // Unsafe: does not limit input size
fgets
#include <stdio.h>
int main() {
char name[10];
printf("Enter your name: ");
fgets(name, sizeof(name), stdin); // Safe: input size is limited
// Remove potential newline character
name[strcspn(name, "\n")] = 0;
printf("Hello, %s!\n", name);
return 0;
}
strcspn, strlen, strcat
#include <stdio.h>
#include <string.h>
int main() {
char str[20];
// String manipulation
int len = strlen(str);
printf("String length: %d\n", len);
return 0;
}
Dynamic Char Array
#include <stdio.h>
#include <stdlib.h>
int main() {
int size = 256; // Define the maximum size of the input
char *str = malloc(size); // Dynamically allocate memory for the string
// Function declaration
int multiply(int x, int y);
int main() {
int result = multiply(10, 5);
printf("Result: %d\n", result);
return 0;
}
// Function definition
int multiply(int x, int y) {
return x * y; // Return the product of x and y
}
#include <stdio.h>
#include <string.h>
int main() {
int height;
return 0;
}
Structs
Structs
• Structs in C programming are composite data types that allow you
to encapsulate multiple data items of potentially different types
into a single cohesive unit.
• Composite Type:
• A struct (short for "structure") is a user-defined data type in C that groups
together different data items (called members) under a single name.
• Encapsulation:
• Structs help in encapsulating related data items, making it easier to
manage and organize data in complex programs.
Example
struct Person {
char name[50];
int age;
float height;
};
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
Compile and Run
use g++
instead of gcc
Datatypes
Fundamental Data Types
• int: Integer type
• char: Character type
• float: Single precision floating-point type
• double: Double precision floating-point type
• bool: Boolean type (true/false)
Derived Datatypes
• Arrays
• Pointers
• References
User-defined Datatypes
• Structures (struct)
• Enumerations (enum)
• Classes (class)
Namespaces
Namespaces
• Avoid name collisions
• Organize code into logical groups
Header files
• Declarations:
• Header files are used to declare the interfaces to your code, including
functions, classes, constants, and macros.
• Inclusions:
• They are included in other files using the #include directive to share
declarations across multiple source files without redefining them.
mymath.h
#ifndef MYMATH_H
rPevents multiple
#define MYMATH_H inclusions of the same
header file using
namespace mymath {
int max(int a, int b);
}
#endif
Source files: .cpp
• Provides the body of the functions declared in the header files.
• Class Method Definitions: Implements the methods of the classes
declared in the header files.
• Local Functions: Defines any additional functions that are not
exposed to other parts of the program.
mymath.cpp
#include "mymath.h"
Ensure that header
file is consistant with
namespace mymath { cpp file
int main() {
std::cout << "Hello, World!" << std::endl;
std::cout << mymath::max(5, 5) << std::endl;
return 0;
}
Namespaces
#include <iostream> Possible to have max –
method in two different
#include "mymath1.h" namespaces, no name
collisions
#include "mymath2.h"
int main() {
std::cout << mymath1::max(5, 5) << std::endl;
std::cout << mymath2::max(5, 5) << std::endl;
return 0;
}
Basic Input and Output
IOStream
• Including the iostream Library
• Output with cout
• std::cout << "Hello, World!" << std::endl;
• Input with cin
• int age;
• std::cout << "Enter your age: ";
• std::cin >> age;
Functions in C++
Function in C++
• A function is a block of code that performs a specific task.
• Functions help in code reusability and modularity.
• Function
• int add(int a, int b) {
• return a + b;
• }
Function Overloading
• Function overloading allows multiple functions to have the same
name with different parameters.
• It helps improve code readability and usability.
• Functions must differ in the type or number of parameters.
• Return type alone is not sufficient to overload a function.
Example
int add(int a, int b) {
return a + b;
}
display(5); // Output: a: 5, b: 10
display(5, 20); // Output: a: 5, b: 20
Example
void greet(std::string name = "Guest") {
std::cout << "Hello, " << name << "!" << std::endl;
}
// Creating an object
int main() {
Animal animal1;
animal1.eat(); // Output: I can eat!
return 0;
}
class Student { int main() {
private:
std::string name; Student student1;
int age;
student1.setName("Alice");
public:
// Setter for name student1.setAge(20);
void setName(std::string n) {
name = n; std::cout << "Name: " <<
} student1.getName() << std::endl;
// Derived class
class Dog : public Animal {
public:
void bark() {
std::cout << "I can bark!" << std::endl;
}
};
int main() {
Dog dog1;
dog1.eat(); // Output: I can eat!
dog1.bark(); // Output: I can bark!
return 0;
}
Polymorphism
// Base class
class Shape {
public:
virtual void draw() {
std::cout << "Drawing Shape" << std::endl;
}
};
// Derived class
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing Circle" << std::endl;
}
};
int main() {
Shape* shape1 = new Shape();
Shape* shape2 = new Circle();
delete shape1;
delete shape2;
return 0;
}
Abstract class
// Base class
class Shape {
public:
virtual void draw() = 0;
};
// Derived class
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing Circle" << std::endl;
}
};
int main() {
Shape* shape1 = new Shape();
Shape* shape2 = new Circle();
delete shape1;
delete shape2;
return 0;
}
Dynamic Memory Handling
Using heap with int
#include <iostream>
int main() {
// Step 1: Allocate memory on the heap
int* ptr = new int;
return 0;
}
Memory leak
#include <iostream>
int main() {
// Step 1: Allocate memory on the heap
int* ptr = nullptr;
return 0;
}
Templates
Templates
• Templates in C++ are a powerful feature that allows for generic
programming.
• Enable functions and classes to operate with any data type
without being rewritten for each type
• There are two basic main types of templates
• function templates
• class templates
Function Template
#include <iostream>
// Function template
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
int intResult = add(3, 4); // T is int
double doubleResult = add(2.5, 3.1); // T is double
std::cout << "intResult: " << intResult << std::endl; // Outputs: intResult: 7
std::cout << "doubleResult: " << doubleResult << std::endl; // Outputs: doubleResult: 5.6
return 0;
}
Class Template
#include <iostream>
// Class template
template <typename T>
class Container {
private:
T value;
public:
Container(T val) : value(val) {}
void setValue(T val) { value = val; }
T getValue() { return value; }
};
int main() {
Container<int> intContainer(42); // T is int
Container<double> doubleContainer(3.14); // T is double
std::cout << "intContainer: " << intContainer.getValue() << std::endl; // Outputs: intContainer: 42
std::cout << "doubleContainer: " << doubleContainer.getValue() << std::endl; // Outputs: doubleContainer: 3.14
return 0;
}
#include <iostream> // Print the list elements again
std::cout << "List elements after push_front: ";
#include <list> for (int value : *myList) {
std::cout << value << " ";
}
int main() {
std::cout << std::endl;
// Create a list of integers on the heap
std::list<int>* myList = new std::list<int>; // Delete an element by value
myList->remove(20); // Remove 20 from the list
// Insert elements into the list // Print the list elements again
std::cout << "List elements after removal: ";
myList->push_back(10); // Add 10 to the end for (int value : *myList) {
myList->push_back(20); // Add 20 to the end std::cout << value << " ";
}
myList->push_back(30); // Add 30 to the end std::cout << std::endl;
int main() {
auto x = 5; // int
auto y = 3.14; // double
auto str = "Hello"; // const char*
return 0;
}
range
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
return 0;
}
Smart Pointers
• unique_ptr represents unique ownership of a resource.
• Only one unique_ptr can own a resource at a time
• When the unique_ptr goes out of scope, it automatically deletes the
resource.
• shared_ptr allows multiple pointers to share ownership of a resource.
• The resource is deleted when the last shared_ptr owning it is destroyed.
Problem
#include <iostream>
int main() {
if (true) {
int* ptr = new int(10); // Allocate memory on the heap
std::cout << "Value: " << *ptr << std::endl;
// Forgetting to delete the allocated memory
}
// Memory allocated inside the if block is not freed, causing a memory leak
return 0;
}
Fix: g++ -std=c++14 *.cpp -o myapp
#include <iostream>
#include <memory>
int main() {
if (true) {
std::unique_ptr<int> ptr = std::make_unique<int>(10); // Allocate memory on the heap using unique_ptr
std::cout << "Value: " << *ptr << std::endl;
// No need to manually delete, unique_ptr automatically deletes the memory when it goes out of scope
}
// Memory allocated inside the if block is automatically freed when the unique_ptr goes out of scope
return 0;
}
shared_ptr
#include <iostream>
#include <memory> // For shared_ptr
class MyClass {
public:
MyClass(int value) : value(value) {
std::cout << "MyClass constructor called with value: " << value << std::endl;
}
~MyClass() {
std::cout << "MyClass destructor called for value: " << value << std::endl;
}
int getValue() const {
return value;
}
private:
int value;
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(42);
if(true) {
std::shared_ptr<MyClass> ptr2 = ptr1;
std::cout << "Value through ptr2: " << ptr2->getValue() << std::endl;
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
}
std::cout << "Value through ptr1: " << ptr1->getValue() << std::endl;
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
return 0;
}
Lambdas
• An anonymous function that can capture variables from its
surrounding scope.
• Basic Syntax:
• [ capture ] ( params ) -> ret { body }
• Capture Clause ([]): Specifies which variables from the surrounding scope
are captured and how.
• Parameter List (( params )): Defines the parameters the lambda takes.
• Return Type (-> ret) (optional): Specifies the return type. If omitted, the
compiler deduces it.
• Body ({ body }): Contains the code to be executed when the lambda is
called.
Example
#include <iostream>
#include <functional>
int main() {
// Define the lambda function with std::function
std::function<int(int, int)> add = [](int a, int b) -> int {
return a + b;
};
return 0;
}
Using auto
#include <iostream>
int main() {
// Define the lambda function
auto add = [](int a, int b) -> int {
return a + b;
};
return 0;
}
Using callbacks
#include <iostream>
#include <functional>
int main() {
// Define the lambda function for summing two numbers
auto sum = [](int a, int b) -> int {
return a + b;
};
return 0;
}
Using callbacks with anonymous lambdas
#include <iostream>
#include <functional>
int main() {
// Use an anonymous lambda function for summing as a callback
performOperation(5, 3, [](int a, int b) -> int {
return a + b;
}); // Should print "The result of the operation is: 8"
return 0;
}
#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <functional>
// Function that reads a file in a different thread and invokes the callback with the file content
void fileRead(const std::string& filePath, std::function<void(const std::string&)> callback) {
// Create a thread to read the file
std::thread([filePath, callback]() {
std::ifstream inputFile(filePath);
if (!inputFile) {
std::cerr << "Unable to open file: " << filePath << std::endl;
return;
}
std::string content;
std::string line;
while (std::getline(inputFile, line)) {
content += line + '\n';
}
allows the thread to run independently
inputFile.close(); from the std::thread object that originally
// Invoke the callback with the file content
callback(content);
represented it. After calling detach, the
}
}).detach(); thread becomes a daemon thread
int main() {
// Example usage of fileRead function
fileRead("path/to/foo.txt", [](const std::string& content) {
std::cout << "File content:\n" << content << std::endl;
});
return 0;
}