# C/C++

Welcome to the C Fundamentals course. In this course, we will cover the basics of the C programming language, as well as some more advanced topics. We will start by discussing how to create variables, use conditions and loops, and import libraries. By the end of this course, you should have a solid understanding of the basics of C and be ready to start writing your own programs.

## Variables

In C, a variable is a way to store a value or a reference to an object. To create a variable, you need to declare it with a specific data type and give it a name, for example:

```c
int x = 5; // Integer variable
float pi = 3.14; // Floating-point variable
char grade = 'A'; // Character variable
double temperature = 25.5; // Double-precision floating-point variable
unsigned int count = 10; // Unsigned integer variable
short int smallNumber = 100; // Short integer variable
long int largeNumber = 1000000; // Long integer variable

// Print the values of the variables
printf("Integer x: %d\n", x);
printf("Floating-point pi: %.2f\n", pi);
printf("Character grade: %c\n", grade);
printf("Double temperature: %.2f\n", temperature);
printf("Unsigned int count: %u\n", count);
printf("Short int smallNumber: %d\n", smallNumber);
printf("Long int largeNumber: %ld\n", largeNumber);
```

This creates a variable named x with the data type `int` and assigns it the value of 5.

* `float` is used for floating-point numbers.
* `char` is used for single characters.
* `double` is used for double-precision floating-point numbers.
* `unsigned int` is used for non-negative integers.
* `short int` is used for short integers.
* `long int` is used for long integers.

### Vector (C++)

In C++, vectors are dynamic arrays that provide flexibility in managing collections of elements.

* `push_back`: Appends a new element to the end of the vector.
* `pop_back`: Removes the last element from the end of the vector.
* `insert`: Inserts elements at a specified position in the vector.
* `erase`: Removes elements from the vector at a specified position or within a range.
* `clear`: Removes all elements from the vector, leaving it with a size of 0.
* `size`: Returns the number of elements in the vector.
* `empty`: Checks if the vector is empty.
* `resize`: Changes the size of the vector, optionally filling new elements with a specified value.
* `front`: Returns a reference to the first element in the vector.
* `back`: Returns a reference to the last element in the vector.

Let's look at an example:

```cpp
#include <iostream>
#include <vector>

int main() {
    // Creating a vector of integers
    std::vector<int> myVector;

    // Adding elements to the vector using push_back method
    myVector.push_back(10);
    myVector.push_back(20);
    myVector.push_back(30);

    // Accessing elements using index
    std::cout << "Elements in the vector: ";
    for (int i = 0; i < myVector.size(); ++i) {
        std::cout << myVector[i] << " ";
    }
    return 0;
}
```

1. **Vector Declaration:** `std::vector<int> myVector;` declares a vector named `myVector` that can store integers.
2. **Adding Elements:** `myVector.push_back(10);` adds the value 10 to the end of the vector. Similarly, we add 20 and 30.
3. **Accessing Elements:** The `for` loop iterates through the vector using its size and prints each element.

This example demonstrates the basic usage of vectors, including declaration, addition of elements using `push_back`, and accessing elements using an index. Vectors in C++ provide dynamic sizing and convenient methods for managing collections.

## Conditions

Conditions are used to make decisions in a program. The most common type of condition is the if-else statement. For example:

```c
int x = 5;
if (x > 0) {
    printf("x is positive");
} else {
    printf("x is not positive");
}
```

This code checks if the value of x is greater than 0. If it is, the program will print "x is positive", otherwise it will print "x is not positive".

## Loops

Loops are used to repeat a block of code multiple times. The two most common types of loops in C are the for loop and the while loop.

The for loop is used to execute a block of code a fixed number of times. For example:

```c
for (int i = 0; i < 5; i++) {
    printf("%d\n", i);
}
```

This code will print the numbers from 0 to 4, because the condition (i < 5) is true for each iteration of the loop.

The while loop is used to execute a block of code as long as a certain condition is true. For example:

```c
int x = 5;
while (x > 0) {
    printf("%d\n", x);
    x--;
}
```

This code will print the numbers from 5 down to 1, because the condition (x > 0) is true for each iteration of the loop.

## Libraries

C has a large number of libraries that can be imported to add additional functionality to your programs. To import a library, you use the `#include` preprocessor directive followed by the name of the library. For example:

```c
#include <stdio.h>
```

This imports the standard input/output library, which provides functions such as `printf()` and `scanf()`.

Once a library is imported, you can use its functions by referencing them with the library name as a prefix. For example:

```c
#include <stdio.h>
int main() {
    printf("Hello, World!");
    return 0;
}
```

This code imports the standard input/output library and then uses the printf() function to print the string "Hello, World!" on the screen.

## Pointers

Pointers are one of the most powerful features of C, they allow you to directly manipulate memory and access variables by their memory address. Pointers are declared using the `*` operator, for example:

```c
int x = 5;
int *ptr = &x;
```

This declares a pointer `ptr` that points to the memory address of the variable `x`. You can then use the pointer to manipulate the value of the variable it points to, or to allocate and deallocate memory dynamically during runtime.

```c
int x = 5;
int *ptr = &x;
*ptr = 10; // x now has the value 10
```

```c
int *ptr = (int *) malloc(sizeof(int));
*ptr = 5;
free(ptr);
```

The first example uses the pointer `ptr` to change the value of the variable `x` and the second one uses the `malloc()` function to dynamically allocate memory for an integer and the `free()` function to deallocate the memory once it's no longer needed.

This code demonstrates the use of pointers in C. It defines three functions, each of which takes a pointer as its argument. The first function, `edit_int`, takes a pointer to an integer and changes its value to `128`. The second function, `edit_char`, takes a pointer to a character and changes its value to `Z`. The third function, `edit_string`, takes a pointer to a pointer to a character and changes the value of the pointer to a new string `Foxtrot`. The main function declares an integer, a character, and a string, and passes pointers to each of them to the corresponding functions. It then prints out the original and modified values of each variable.

```c
#include <stdio.h>

void edit_int(int *);
void edit_char(char *);
void edit_string (char **);

int main(void) {
    int nb = 1;
    char letter = 'A';
    char *string = "Alpha";

    printf("First value: %d\n", nb);
    edit_int(&nb);
    printf("Second value: %d\n", nb);
    putchar('\n');

    printf("First value: %c\n", letter);
    edit_char(&letter);
    printf("Second value: %c\n", letter);
    putchar('\n');

    printf("First value: %s\n", string);
    edit_string(&string);
    printf("Second value: %s\n", string);
    putchar('\n');

    return 0;
}

void edit_int(int *nb) { *nb = 128; }

void edit_char(char *character) { *character = 'Z'; }

void edit_string(char **string) { *string = "Foxtrot"; }
```

## Structures

Structures are a way of grouping together different variables of different data types, similar to objects in object-oriented languages. They allow you to create custom data types with several fields, for example:

```c
struct Person {
    char name[50];
    int age;
    float salary;
};
```

This defines a new data type named `Person` with three fields: a character array named `name`, an integer named `age`, and a float named `salary`.

```c
int main() {
    struct Person person;

    printf("Enter name: ");
    scanf("%s", person.name);

    printf("Enter age: ");
    scanf("%d", &person.age);

    printf("Enter salary: ");
    scanf("%f", &person.salary);

    printf("\n%s is %d years old and earns $%.2f.\n", person.name, person.age, person.salary);

    return 0;
}
```

In this example, the program creates a new `Person` structure named `person`. The program prompts the user to enter the person's name, age, and salary, and then stores the input in the appropriate fields of the `person` structure. Finally, the program prints out the person's information using the `printf()` function.

## Functions

Functions in C work similarly to how they do in other programming languages, they allow you to define a block of code that can be reused multiple times in your program. Functions in C must be declared before they are used, this is done by writing a function prototype that specifies the return type, name and parameters of the function.

In C, a function is a self-contained block of code that performs a specific task. Functions are used to divide a large program into smaller, manageable parts. Each function has a name, parameters, and a return type, and it can be called (invoked) from other parts of the program.

### Function Definition

A function in C is defined with the following syntax:

```c
return_type function_name(parameter_type parameter_name, ... ) {
    // Function body
    // Code to perform a specific task
    return return_value;
}
```

* **return\_type**: Specifies the type of data that the function will return. If a function doesn't return anything, you can use `void` as the return type.
* **function\_name**: The name of the function, which should be a unique identifier within the program.
* **parameter\_type**: The type of each parameter the function accepts.
* **parameter\_name**: The name of each parameter used to reference values passed to the function.
* **Function body**: Contains the code that defines what the function does.
* **return\_value**: The value to be returned by the function. If the return type is `void`, this part is omitted.

### Example: `add` Function

Let's look at an example of a simple `add` function that takes two integers and returns their sum:

```c
int add(int a, int b) {
    int sum = a + b;
    return sum;
}
```

In this example:

* `int` is the return type, indicating that the function will return an integer.
* `add` is the function name.
* `(int a, int b)` are the parameters. The function takes two integer parameters, `a` and `b`.
* The function body calculates the sum of `a` and `b` and returns the result (`sum` variable) using the `return` statement.

### Function Prototypes

Before you use a function in C, it must be declared or defined. To declare a function, you can provide a function prototype. A function prototype specifies the function's name, return type, and parameters without defining the full function. This allows you to use the function before its actual implementation.

Here's a function prototype for the `add` function:

```c
int add(int a, int b);
```

This prototype informs the compiler that there is a function called `add` that takes two integers as parameters and returns an integer. You can place function prototypes at the beginning of your source code or include them in header files for use in multiple source files.

### Using the `add` Function

To use the `add` function in your program, you can call it from the `main` function:

```c
#include <stdio.h>

int main() {
    int result = add(5, 3);
    printf("Sum: %d\n", result);
    return 0;
}
```

In this code, we include the `<stdio.h>` header for the `printf` function and call the `add` function to calculate the sum of 5 and 3.

We've covered the basics of functions in C, including their definition, parameters, return values, and function prototypes. Functions are an essential part of C programming, enabling code organization, reusability, and modularity. They allow you to break down complex tasks into manageable components, making your code more readable and maintainable.

## Class (C++)

Classes are an essential part of object-oriented programming, and they allow you to create custom data types and encapsulate both data and behavior into a single unit. We'll use the example code you provided as a starting point to explain various concepts related to C++ classes.

### What is a Class?

In C++, a class is a blueprint for creating objects. It defines the structure and behavior of objects that will be created based on it. A class can contain data members (also called attributes or properties) and member functions (methods) that operate on these data members.

### Example: Auth Class

Let's start by examining the code you provided. The `Auth` class represents a simple authentication system with a predefined username and password. Here's the code with explanations:

```cpp
#include <iostream>
#include <string>

using namespace std;

class Auth {
  private:
    string username = "fastiraz"; // Private data member for the username
    string password = "Sup3r_S3cr3t_P4ssw0rd"; // Private data member for the password

  public:
    bool login(string usr, string pwd) {
      if (usr == this->username && pwd == this->password) {
        return true; // Successful login
      } else {
        return false; // Failed login
      }
    }
};
```

In this code, we define a class called `Auth`, which contains:

* **Private Data Members**: The `username` and `password` are private data members of the class, which means they can only be accessed within the class itself. This encapsulates sensitive information, making it inaccessible from outside the class.
* **Public Member Function**: The `login` function is a public member function that allows external code to attempt a login. It takes two arguments, `usr` (the entered username) and `pwd` (the entered password). It compares these values with the stored username and password, and if they match, it returns `true`, indicating a successful login; otherwise, it returns `false`.

### Using the Auth Class

The `main` function demonstrates how to create an instance of the `Auth` class and use it for authentication:

```cpp
int main(void) {
  string usr, pwd;

  cout << "Username: ";
  cin >> usr;
  cout << "Password: ";
  cin >> pwd;

  Auth auth; // Create an instance of the Auth class

  if (auth.login(usr, pwd)) {
    cout << "[Successful!]" << endl;
  } else {
    cout << "[Failed!]" << endl;
  }

  return 0;
}
```

In this `main` function, we perform the following steps:

1. Prompt the user for a username and password.
2. Create an instance of the `Auth` class named `auth`.
3. Call the `login` method of the `auth` instance to attempt authentication.
4. Based on the result of the `login` method, display a message indicating whether the login was successful or not.

## Header files

C also allows you to create your own libraries, also called header files, which can be included in your program to reuse code and organize your program in a more modular way. To create a library, you can create a header file with the extension `.h` that contains the function prototypes and any global variables or constants. Then, in a separate file with the extension `.c`, you can provide the implementation of the functions.

By the end of this course, you should have a good understanding of the C programming language and be able to write programs that make use of variables, conditions, loops, functions, pointers, structures, dynamic memory allocation, and more.

## Bitwise operations

C provides a set of bitwise operators such as `&`, `|`, `^`, `~`, `<<`, and `>>` that allow you to manipulate individual bits of a variable. These operators can be used to optimize certain types of algorithms, perform low-level operations on hardware, or encode and decode data.

```c
unsigned char x = 10; // binary: 00001010
unsigned char y = 5;  // binary: 00000101
unsigned char z = x & y;  // binary: 00000010
```

In this example, the `&` operator is used to perform a bitwise AND operation on the variables `x` and `y`, resulting in a new variable `z` with the value 2 (binary: 00000010).

## typedef

In C, the `typedef` keyword is a powerful tool that allows you to create your own custom data types, giving you the ability to enhance code readability and maintainability. This course will explain what `typedef` is, how it works, and provide practical examples to help you understand its usage.

### What is `typedef`?

In C, `typedef` is a keyword used to define custom data types. It allows you to create aliases or synonyms for existing data types, making your code more readable and expressive.

### Defining Custom Data Types

The syntax for creating a custom data type using `typedef` is as follows:

```c
typedef existing_data_type new_data_type;
```

* `existing_data_type`: The data type for which you want to create an alias.
* `new_data_type`: The alias or synonym you want to create for the existing data type.

### Using `typedef` with Basic Data Types

#### Example 1: Creating an Alias for `int`

```c
typedef int Integer;
```

In this example, we create an alias "Integer" for the `int` data type. Now, you can use `Integer` instead of `int` in your code.

#### Example 2: Creating an Alias for `char`

```c
typedef char Character;
```

Here, we create an alias "Character" for the `char` data type.

### Creating Complex Custom Types

You can also use `typedef` to create complex custom data types. For instance:

#### Example 3: Creating a Structure Alias

```c
struct Date {
    int day;
    int month;
    int year;
};

typedef struct Date MyDate;
```

In this example, we define a custom structure `Date` and then create an alias `MyDate` for it using `typedef`.

### Benefits of `typedef`

* **Improved Code Readability**: `typedef` can make your code more descriptive and easier to understand by using meaningful names for data types.
* **Easy Maintenance**: If you need to change the underlying data type, you only have to update it in one place where the `typedef` is defined.
* **Enhanced Portability**: Using `typedef` can help make your code more portable since it abstracts the underlying data types, making it easier to adapt to different platforms.

## Enum

In C, an `enum` is a user-defined data type used to create a set of named integer constants. It provides a way to represent a set of values with meaningful names, making code more readable and maintainable. This course will explain what `enum` is, how it works, and provide practical examples to help you understand its usage.

Enums, short for "enumerations," are a data type in C that allow you to create a set of named integral constants. They make your code more readable and maintainable by providing descriptive names for values rather than using raw numbers.

### Declaring Enums

In C, you declare an enum using the `enum` keyword followed by a list of constant names enclosed in curly braces.

```c
enum Days {
    SUNDAY,    // 0
    MONDAY,    // 1
    TUESDAY,   // 2
    WEDNESDAY, // 3
    THURSDAY,  // 4
    FRIDAY,    // 5
    SATURDAY   // 6
};
```

In this example, `Days` is the enum type, and the constants `SUNDAY`, `MONDAY`, etc., are assigned consecutive integer values starting from 0.

### Accessing Enum Constants

You can access enum constants by using the enum type name followed by the constant name.

```c
enum Days today = TUESDAY;
```

### Enum Constants and Values

While the example above assigns integer values to enum constants by default, you can also explicitly set the values.

```c
enum Colors {
    RED = 1,
    GREEN = 2,
    BLUE = 4
};
```

In this case, `RED` will have a value of 1, `GREEN` will have 2, and `BLUE` will have 4.

### Using Enums in Switch Statements

Enums are often used in switch statements for improved code readability.

```c
enum Days day = WEDNESDAY;

switch (day) {
    case MONDAY:
        printf("It's Monday!");
        break;
    case WEDNESDAY:
        printf("It's Wednesday!");
        break;
    default:
        printf("It's not a special day.");
        break;
}
```

### Benefits of Enums

* **Readability**: Enums make code more readable as constant names are self-explanatory.
* **Type Safety**: You can't assign arbitrary values to an enum; it only accepts values defined within the enum.
* **Maintenance**: When constants change, you only need to update the enum definition.
* **Compile-Time Constants**: Enums are evaluated at compile-time.

## Preprocessor Macros

C also provides a preprocessor that allows you to define macros using the `#define` directive. Macros are a way to create simple and efficient substitutes for repetitive or complex expressions, or to define constants that can be easily changed later.

```c
#define PI 3.14
#define MIN(a,b) ((a) < (b) ? (a) : (b))
```

In this example, the first macro defines the constant PI as 3.14, and the second macro defines a MIN function that returns the minimum of two values.

Here's another helpful illustration:

```cpp
#define output(x) std::cout << x << std::endl;
#define input(x) std::cin >> x;
```

In this instance, we are defining custom print and read functions (`input` and `output`) to simplify content display and input.

You can utilize them as follows:

```cpp
int main (void) {
  string usr, pwd;

  output("Username: ");
  input(usr);
  output("Password: ");
  input(pwd);

  Auth auth;
  if (auth.login(usr, pwd)) {
    output("[Successful!]");
  } else {
    output("[Failed!]");
  }
  
  return 0;
}
```

### Conditional Compilation <a href="#conditional" id="conditional"></a>

In C programming, you can instruct the preprocessor whether to include a block of code or not. To do so, conditional directives can be used.

It's similar to a `if` statement with one major difference.

The `if` statement is tested during the execution time to check whether a block of code should be executed or not whereas, the conditionals are used to include (or skip) a block of code in your program before execution.

***

### Uses of Conditional

* use different code depending on the machine, operating system
* compile the same source file in two different programs
* to exclude certain code from the program but to keep it as a reference for future purposes

### How to use conditional?

To use conditional, `#ifdef`, `#if`, `#defined`, `#else` and `#elif` directives are used.

#### ifdef Directive

```c
#ifdef MACRO     
   // conditional codes
#endif
```

Here, the conditional codes are included in the program only if `MACRO` is defined.

#### #if, #elif and #else Directive

```c
#if expression
   // conditional codes
#endif
```

Here, expression is an expression of integer type (can be integers, characters, arithmetic expression, macros, and so on).

The conditional codes are included in the program only if the expression is evaluated to a non-zero value.

The optional `#else` directive can be used with `#if` directive.

```c
#if expression
   conditional codes if expression is non-zero
#else
   conditional if expression is 0
#endif
```

You can also add nested conditional to your `#if...#else` using `#elif`

```c
#if expression
    // conditional codes if expression is non-zero
#elif expression1
    // conditional codes if expression is non-zero
#elif expression2
    // conditional codes if expression is non-zero
#else
    // conditional if all expressions are 0
#endif
```

***

### #define&#x20;

The special operator #defined is used to test whether a certain macro is defined or not. It's often used with #if directive.

```c
#if defined BUFFER_SIZE && BUFFER_SIZE >= 2048
  // codes
```

### Predefined Macros <a href="#predefined-macros" id="predefined-macros"></a>

Here are some predefined macros in C programming.

| Macro      | Value                                                            |
| ---------- | ---------------------------------------------------------------- |
| `__DATE__` | A string containing the current date.                            |
| `__FILE__` | A string containing the file name.                               |
| `__LINE__` | An integer representing the current line number.                 |
| `__STDC__` | If follows ANSI standard C, then the value is a nonzero integer. |
| `__TIME__` | A string containing the current time.                            |

The following program outputs the current time using `__TIME__` macro.

```c
#include <stdio.h>
int main() {
   printf("Current time: %s",__TIME__);   
}
```

###

## Unions

Unions are a special type of data structure in C that allows you to store different data types in the same memory location. This can be useful for creating efficient and flexible data structures, or for performing type punning.

```c
union Number {
    int i;
    float f;
    double d;
};
```

In this example, a union named `Number` is defined that can store an integer, a float, or a double in the same memory location.

## File handling

C also provides a standard library for file handling, which allows you to read and write data from files using file streams. File streams are similar to the standard input and output streams, but they operate on files instead of the console.

```c
#include <stdio.h>

int main() {
    FILE *file;
    file = fopen("example.txt", "w");
    fprintf(file, "Writing to a file in C");
    fclose(file);
    return 0;
}
```

In this example, the `fopen()` function is used to open a file named "example.txt" in write mode, the `fprintf()` function is used to write a string to the file, and the `fclose()` function is used to close the file.

## Pipes

### Understanding Pipes

In C, a pipe is a form of interprocess communication (IPC) that allows two processes to communicate with each other. One process writes data to the pipe, while the other process reads the data from the pipe. A pipe can be thought of as a unidirectional data channel.

A pipe is created using the `pipe()` function. This function takes an array of two integers as an argument, which represent the read and write ends of the pipe. The `pipe()` function returns 0 on success and -1 on failure.

```c
#include <unistd.h>

int pipe(int pipefd[2]);
```

The `pipefd` parameter is a two-element array that will be populated with the file descriptors for the read and write ends of the pipe. For example:

```c
int pipefd[2];
if (pipe(pipefd) == -1) {
    perror("pipe");
    exit(EXIT_FAILURE);
}
```

This code creates a pipe and stores the read and write file descriptors in `pipefd`.

### Using Pipes

Once a pipe has been created, the two processes can use it to communicate with each other. The process that wants to write data to the pipe writes to the write end of the pipe, while the process that wants to read data from the pipe reads from the read end of the pipe.

Here is an example of how to write data to a pipe:

```c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    int pipefd[2];
    pid_t pid;
    char buffer[20];

    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    pid = fork();

    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        /* Child process */
        close(pipefd[0]); /* Close unused read end */
        write(pipefd[1], "Hello, world!", 13);
        exit(EXIT_SUCCESS);
    } else {
        /* Parent process */
        close(pipefd[1]); /* Close unused write end */
        read(pipefd[0], buffer, 13);
        printf("%s\n", buffer);
        exit(EXIT_SUCCESS);
    }

    return 0;
}
```

This code creates a pipe, forks a child process, and then writes the string "Hello, world!" to the pipe from the child process. The parent process reads the string from the pipe and prints it to the console.

Pipes are an important form of interprocess communication in C. They provide a simple way for two processes to communicate with each other. In this course, we have covered the basics of how pipes work, how to create and use them, and provided some code examples to illustrate their usage. With this knowledge, you can start using pipes in your own C programs to facilitate communication between different processes.

## Signals

### Understanding Signals

Signals are an important concept in C programming, as they allow a process to be interrupted by the operating system when a certain event occurs. Some common events that can trigger signals include a user pressing Ctrl-C, the process running out of memory, or the process receiving a specific signal from another process.

When a signal is sent to a process, the process is interrupted and the signal handler function is called. The signal handler function is responsible for handling the signal and responding appropriately. This can include terminating the process, ignoring the signal, or performing some other action.

### Signal Handling in C

Signal handling in C involves defining a signal handler function that will be called when a signal is received by the process. The `signal()` function is used to set up the signal handler function, and takes two arguments: the signal number to handle, and the name of the signal handler function.

```c
#include <signal.h>

void sigint_handler(int signum) {
    printf("Caught signal %d\n", signum);
}

int main(void) {
    signal(SIGINT, sigint_handler);

    while (1) {
        printf("Waiting for signal...\n");
        sleep(1);
    }

    return 0;
}
```

In the example above, we define a signal handler function `sigint_handler()` that will be called when the `SIGINT` signal is received. We then set up the signal handler function using the `signal()` function. Finally, we enter an infinite loop that waits for signals to be received.

### Sending Signals in C

Signals can also be sent from one process to another using the `kill()` function. The `kill()` function takes two arguments: the process ID of the receiving process, and the signal number to send.

```c
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    pid_t pid;
    int signum;

    if (argc != 3) {
        printf("Usage: %s <pid> <signum>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    pid = atoi(argv[1]);
    signum = atoi(argv[2]);

    if (kill(pid, signum) == -1) {
        perror("kill");
        exit(EXIT_FAILURE);
    }

    return 0;
}
```

In the example above, we use the `kill()` function to send a signal to a process with a specified process ID. We take the process ID and signal number as command line arguments.

### Handling Multiple Signals

It is possible to handle multiple signals in a single signal handler function using the `sigaction()` function. The `sigaction()` function allows us to specify a structure that contains information about how to handle the signal, including a pointer to the signal handler function.

```c
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void sig_handler(int signum) {
    printf("Caught signal %d\n", signum);
}

int main() {
    struct sigaction sa;

    sa.sa_handler = sig_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);

    while {
        printf("Waiting for signals...\n");
        sleep(1);
    }

    return 0;
}
```

In the example above, we define a signal handler function `sig_handler()` that will be called when either the `SIGINT` or `SIGTERM` signal is received. We then use the `sigaction()` function to set up the signal handler function for both signals.

Signals are an important mechanism for interprocess communication and handling events in C. By understanding how signals work, how to send and receive signals, and how to handle signals, you can write robust and reliable C programs that respond appropriately to events and signals sent by the operating system.

In this course, we have covered the basics of signals in C, including how to handle single and multiple signals, and how to send signals to other processes. We encourage you to explore further and experiment with signals in your own C programs.

## Semaphore

### Introduction

Semaphore is a synchronization primitive in operating systems that allows multiple processes/threads to access shared resources in a mutually exclusive manner. Semaphore can be used to implement locks, mutual exclusion, and coordination between multiple processes/threads.

In this course, we will learn about semaphores in C programming language, including their definition, use, and implementation using code examples.

### Definition

A semaphore is an integer value that is used to control access to a shared resource in a concurrent system. It consists of two operations:

* **Wait** (also known as `P` or `down`): The wait operation decrements the value of the semaphore by 1. If the resulting value is negative, the process/thread is blocked until the semaphore value becomes positive again.
* **Signal** (also known as `V` or `up`): The signal operation increments the value of the semaphore by 1. If there are any processes/threads waiting for the semaphore, one of them is unblocked.

### Semaphore implementation in C

In C programming language, semaphores can be implemented using the `sem_t` data type, which is defined in the `semaphore.h` header file.

#### Creating a semaphore

To create a semaphore, we use the `sem_init()` function, which takes three arguments:

```c
int sem_init(sem_t *sem, int pshared, unsigned int value);
```

* `sem`: A pointer to the semaphore variable that is to be initialized.
* `pshared`: A flag that indicates whether the semaphore should be shared between multiple processes or not. A value of `0` means that the semaphore is shared between threads of the same process, while a value of `1` means that the semaphore is shared between multiple processes.
* `value`: The initial value of the semaphore.

Here's an example:

```c
#include <semaphore.h>

sem_t my_sem;

int main() {
    sem_init(&my_sem, 0, 1);
    // ...
}
```

In this example, we create a semaphore `my_sem` with an initial value of `1`, which means that the first process/thread that tries to access the shared resource will be able to do so.

#### Waiting and signaling

To perform the wait and signal operations on a semaphore, we use the `sem_wait()` and `sem_post()` functions, respectively. Both functions take a pointer to the semaphore variable as their argument.

```c
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
```

Here's an example that demonstrates the use of `sem_wait()` and `sem_post()`:

```c
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

sem_t my_sem;

void *worker(void *arg) {
    sem_wait(&my_sem);
    printf("Worker thread acquired the semaphore.\n");
    // ...
    sem_post(&my_sem);
    printf("Worker thread released the semaphore.\n");
    return NULL;
}

int main(void) {
    pthread_t tid;
    sem_init(&my_sem, 0, 1);
    pthread_create(&tid, NULL, worker, NULL);
    // ...
    sem_wait(&my_sem);
    printf("Main thread acquired the semaphore.\n");
    // ...
    sem_post(&my_sem);
    printf("Main thread released the semaphore.\n");
    pthread_join(tid, NULL);
    return 0;
}
```

In this example, we create a semaphore `my_sem` with an initial value of `1`. We then create a worker thread that waits for the semaphore using `sem_wait()`, performs some work, and then signals the semaphore using `sem_post()`. The main thread also waits for the semaphore using `sem_wait()`, performs some work, and then signals the semaphore using `sem_post()`. Both the main thread and the worker thread will be able to access the shared resource in a mutually exclusive manner.

#### Error handling

When working with semaphores, it is important to handle errors that may occur during initialization or operation. The `sem_init()`, `sem_wait()`, and `sem_post()` functions all return `0` on success and `-1` on failure, so we can use this to check for errors.

Here's an example:

```c
#include <stdio.h>
#include <semaphore.h>
#include <errno.h>

sem_t my_sem;

int main(void) {
    if (sem_init(&my_sem, 0, 1) == -1) {
        perror("sem_init");
        return 1;
    }
    // ...
    if (sem_wait(&my_sem) == -1) {
        perror("sem_wait");
        return 1;
    }
    // ...
    if (sem_post(&my_sem) == -1) {
        perror("sem_post");
        return 1;
    }
    // ...
    return 0;
}
```

In this example, we use the `perror()` function to print an error message if `sem_init()`, `sem_wait()`, or `sem_post()` fails. The `errno` variable contains the error code that caused the failure.

In this course, we learned about semaphores in C programming language, including their definition, use, and implementation using code examples. Semaphores are a powerful synchronization primitive that can be used to implement locks, mutual exclusion, and coordination between multiple processes/threads. By understanding semaphores, you will be better equipped to design and implement concurrent systems that are correct, efficient, and reliable.

## goto

In this chapter, we'll explore the `goto` statement in C/C++. The `goto` statement allows altering the flow of control in a program by transferring the execution to a labeled statement within the same function. However, it's essential to use `goto` judiciously as its misuse can lead to less readable and more complex code.

### Syntax

The syntax for `goto` statement is straightforward:

```c
goto label_name;
```

Where `label_name` is an identifier followed by a colon `:` that marks the label in the code.

### Example

Consider the following example:

```c
#include <stdio.h>

int main() {
    int count = 0;
    start: // Label declaration
    printf("Count: %d\n", count);
    count++;
    
    if (count < 5) {
        goto start; // Jump to the 'start' label
    }
    
    printf("End of the loop\n");
    return 0;
}
```

### Explanation

* `count` is initialized to 0.
* The `start` label is declared just before the `printf` statement.
* It prints the value of `count`, increments `count` by 1, and checks if `count` is less than 5.
* If `count` is less than 5, the program uses `goto start;` to jump back to the `start` label and repeats the process.
* Once `count` is not less than 5, the loop ends, and "End of the loop" is printed.

### Output

```bash
Count: 0
Count: 1
Count: 2
Count: 3
Count: 4
End of the loop
```

### Best Practices and Caution

* **Use sparingly**: While `goto` can be useful in certain cases, overuse can lead to code that is hard to follow and maintain.
* **Prefer other control structures**: In most scenarios, loops (`for`, `while`, `do-while`) and conditional statements (`if-else`) are more readable and maintainable than `goto`.

The `goto` statement is a powerful control flow tool in C/C++ programming. However, it should be used cautiously and sparingly to ensure code readability and maintainability.

## Conclusion

C is a powerful and versatile language that is widely used in systems programming, embedded systems, and high-performance computing. It's a great choice for low-level programming, and its popularity and wide availability make it an excellent choice for learning the fundamentals of programming. With this course, you should have a solid understanding of the basics of C and be ready to start writing your own programs and experimenting with advanced features of the language.

{% embed url="<https://media.giphy.com/media/2IudUHdI075HL02Pkk/giphy.gif>" %}
