Introduction To C
Introduction To C
Introduction to C
Accept Reject
This course is given to you for free by the Malcore team: https://m4lc.io/course/c/register
Consider registering, and using Malcore, so we can continue to provide free content for the
entire community. You can also join our Discord server here: https://m4lc.io/course/c/discord
We offer free threat intel in our Discord via our custom designed Discord bot. Join the Discord to
discuss this course in further detail or to ask questions.
Control Flows
C Functions
What the F*ck is a Pointer?
Memory Overview
That's all!
What is C?
In a sentence: C is a powerful general-purpose compiled programming language.
In a more descriptive way: C is a powerful compiled programming language that was developed
by Dennis Ritchie in 1972 at Bell Labs. It was originally designed for systems programming
specifically operating systems. Overtime C became one of the most widely used languages in
existence and was used for a lot more than just operating system development. C is known for
simplicity, efficiency, and system resource control. It is a popular choice for applications that
require high performance or low-level interaction. There are of course issues with C a lot of
people consider it a "broken language" however, we will not get into that debate here.
It is good for you to understand the issues in C as well as the benefits. We will first start with the
issues:
Manual memory management and no garbage collection. Meaning that you must manage
your own memory in your programs, it is not done for you like it is in other languages such as
Python.
Lack of object orientation. There is no OOP in C. Meaning it does not have classes in it.
There is no built-in error handling. C relies on return codes and external error handling.
C's low-level access can provide security issues such as buffer overflows, stack overflows,
and other security issues. These issues can be exploited if not carefully written.
The issues are descriptive and provide a good understanding of what's wrong with the C
language and what can arise for the developer. However, there are of course benefits of C such
as:
Efficiency and speed. C is highly efficient with minimal overhead runtime. Critical
performance applications are ideal to write in C due to its direct memory access and low-level
operation abilities.
C is very portable. It can be ported across multiple platforms easily.
Simplicity. Despite how powerful C is, it is simple. There is a small number of keywords and
the syntax is pretty straightforward.
Extensiveness. There are millions of libraries for C that can do literally anything you can
imagine. It's been around long enough that everything has been created.
Comments:
// this is a comment in C
/*
this is
a
multi line
comment in C
*/
Data types:
Variables:
Variables must always be declared before use. For example:
int a;
float b;
char c;
int a = 5;
char c = 'c';
float b = 1.5;
Constants:
You can declare a constant using the const keyword. IE: const int AGE = 33;
You can also declare them using #define , IE: #define PI 3.14
Operators:
Arithmetic operators:
+ addition
- subtraction
* multiplication
/ division
% modulus
Comparison operators:
== equal to
!= not equal to
Logical operators:
&& logical AND o
|| logical OR
! logical NOT
Arrays
Arrays allow you to store multiple values of the same type, IE:
int numbers[5] = {1,2,3,4,5};
Structures (structs):
struct Person {
char name[50];
int age;
float height;
};
// create a person
struct Person john = {.name = "John", .age = 50, .height = 65.6};
Enumerations (enums):
An enumeration (enum) is a way to assign names to integer values. Example:
enum Weekday {Monday, Tuesday};
enum Weekday today = Monday;
Modifiers:
C offers modifiers that can applied to basic data types. It changes their range and size.
signed : can store both positive and negative integers, IE: signed int num = -5;
unsigned : can store only positive values, IE: unsigned int num = 15;
long : increases the storage size, IE: long int num = 100000;
Typedef:
Range
Data Type Size (bytes) Range (signed) Note
(unsigned)
0-
-2,147,483,648
int 4 4,294,967,29 n/a
- 2,147,483,647
5
3.4E-38 -
float 4 n/a n/a
3.4E+38
1.7E-308 -
double 8 n/a n/a
1.7E+308
Condition statements:
This statement allows execution of a block of code if a specific condition is true. You
may also use an else if to test multiple conditions to be true. Otherwise, the
alternative block of code in the else is executed. Example:
int x = 10;
if (x > 10) {
printf("x is greater than 10");
} else if (x > 5) {
printf("x is greater than 5 less than 10");
} else {
printf("x is less than 5");
}
Switch statements
Execute one block of code among multiple depending on the value of an expression.
Switch statements have a default condition, this condition is executed if none of
expressions match the case. Example:
int day = 2;
switch (day) {
case 1:
printf("Monday");
case 2:
printf("Tuesday");
case 3:
printf("Wednesday);
default:
printf("That's not a f*ckin day...");
}
Loops:
for loop repeats a block of code a known number of times, IE:
while loop is used to repeat code while a condition is evaluated to true, IE:
int i = 0;
while (i < 5) {
printf("%d", i);
i++;
}
do-while loop is similar to a while loop. However, the condition is checked AFTER the loop
has already executed. This guarantees that the loop will run at least once, IE:
int i = 0;
do {
printf("%d", i);
i++;
} while (i < 5);
Jump statements:
int i = 0;
// label
start:
printf("%d", i);
i++;
if (i < 3) {
goto start;
}
NOTE: it is worth noting that goto is usually frowned upon because it can make the code
harder to follow. However, do what you want. It's your code. It is also worth noting that goto
is useful for deep nested loops.
Return statements:
Used to exit a function and return a value from that function call, IE:
if (condition1) { // code
Conditional Tests multiple
else-if } else if (condition2) {
Statement conditions sequentially.
// code } else { // code }
Transfers control to a
Jump goto label; ...
goto labeled part of the code.
Statement label: // code
Use is discouraged.
C Functions
A function in C is a block of code that can be called multiple times throughout the program.
Functions are usually designed to do one thing and do it well. This allows for easily readable
code that provides better maintainability. It allows you to write a chunk of code one time and use
it over and over again.
When declaring a function in C you use a prototype . You must declare a function before use.
This is typically done before the main function of the program. The declaration informs the
compiler about the name, return type, and parameters of the function.
function:
// create a function prototype
int add(int a, int b);
Function definitions specify the actual code that is executed within the function when it is called.
This is a set of instructions that make up the functions logical body:
Once the function has been declared and defined you can call the function by simply calling its
name and passing the required arguments. For example:
It is entirely possible to create a function that returns nothing. In order to do so you will provide it
with the void return type:
void logWelcome() {
printf("Hey nerd!");
}
Recursion is when a function calls itself. This can be useful for solving problems without having
to write extra code. For example:
int factorial(int n) {
if (n == 0) {
return 1;
} else {
return n * factorial(n-1);
}
}
Element Description
Function
Specifies the function's name, return type, and parameters.
Declaration
Function
Contains the code (body) that performs the function's task.
Definition
Function Call Invokes the function, passing the necessary arguments to the function.
Void Function A function that performs an action but does not return any value.
Declaring a pointer
To declare a pointer you specify the data type of the variable followed by an asterisk ( * ) and
finally the pointer name.
// data_type *pointer_name;
int *a;
Initially the pointer will contain an arbitrary memory address and should be assigned a valid
address before use.
Initialize a pointer
To initialize a pointer you assign them the address of another variable using the address of
operator ( & ).
// pointer_name = &variable;
int x = 12;
int *p = &x; // this now holds the address of the `x` variable
The p stores the memory address of the x variable, but not the value of it.
Dereference a pointer
Dereferencing pointers is when you access the value stored at the memory address.
// *pointer_name;
int x = 12;
int *p = &x;
printf("%d", *p); // Dereferencing: prints the value of `x` which is 12.
Null pointers
Null pointers are a pointer that points to nothing or an invalid memory location. When you
initialize a pointer, and it doesn't point to anything it is good practice to initialize it to null.
int *p = NULL;
This indicates that the pointer is not pointing to anything valid yet.
Arrays
An array acts as pointers to the first element in the array. You can use pointers to iterate through
the array. This way you don't have to declare the pointer ( & ) for an array since it's already
technically a pointer.
Pointer arithmetic
Pointer arithmetic is when you navigate through memory locations that are contiguous (such as
arrays) using a pointer.
You can increment and decrement a pointer in order to move to the next value.
You can also add or subtract an integer to move the pointer by that many elements.
Using pointers with functions can allow you to modify a variable or array directly without
returning a value.
int main() {
int x = 5;
incPointer(&x); // pass memory address of x variable
printf("%d", x); // should output 6
return 0;
}
Pointerception
C allows you to point to pointers it's like pointerception but less cool and more confusing.
// data_type **pointer_name
int x = 10; // create an integer variable with value 10
int *p = &x; // create a pointer to that variable
int **pp = &p; // do pointerception and confuse the f*ck out of everyone
There are of course downsides and upsides of pointers. We will start with the downsides:
Pointers can become extremely complex and confusing. They can make your code extremely
hard to read, understand, and debug.
Incorrect use of pointers can lead to memory leaks, segmentation faults, and security
vulnerabilities.
Pointers are very efficient at managing memory and allow dynamic memory allocation.
They provide direct memory access that allows you to manipulate data at very low levels.
Pointer
Description Example
Concept
Pointer
Declares a pointer to store the address of a variable. int *p;
Declaration
Pointer
Initializes a pointer by assigning the address of a variable. p = &x;
Initialization
void
Pointer to Passes pointers to functions, allowing
increment(int
Function them to modify variables or arrays.
*p);
Pointer to
A pointer that points to another pointer. int **pp = &p;
Pointer
As a bonus as mentioned earlier pointers can get extremely complex. So, we will be showing you
an example of a complex pointer and why you need to be careful when creating pointers and
using them in your code. The code below should never be used in production, and never be
used in anything. Read the comments to understand exactly what is going on:
int main() {
// declare an integer and set it to 42
int x = 42;
// WTF AM I DOING
// pointer arithmetic combined with dereferencing
int *****ppppp = &pppp;
// should print 99
printf("x = %d\n", x);
Memory Allocation
Memory allocation is the process of allocating and managing memory during the execution of a
program. There are two main types of memory allocations in C:
Static Memory Allocation : this memory is allocated at compilation time.
Dynamic Memory Allocation : this memory is allocated dynamically at runtime using pointers.
Benefits:
Allows dynamic memory allocation making it possible to only allocate the memory you
need.
Issues:
The memory is not initialized and contains garbage values. This may lead to undefined
behavior if the memory is accessed before initialized.
Provides a risk of memory leaks if you don't free() the memory allocation.
Even though it's faster than calloc() you have to manually initialize it which adds
additional complexity.
calloc() : allocates memory an array and initializes to zero.
Benefits:
Can produce over allocation which can produce memory waste or crashes.
Benefits:
Allows you to resize memory allocation dynamically as long as that memory is already
allocated.
Does not expand the current block, just relocates the existing block somewhere else.
Has the potential for data loss. If it fails it will return NULL .
Has performance overhead because it has to move the memory to another location.
Benefits:
If you forget to free dynamically allocated memory this will result in memory leaks.
When you allocate memory statically you determine the lifetime and size of the variables at
compilation time. These variables memory is allocated and deallocated automatically when the
program starts and ends. Variables declared outside of functions or when using the static
keyword fall into the category. Arrays with a fixed size also fall into this category. An example of
a static memory allocation is as follows:
When you allocate memory dynamically you are doing so with pointers at runtime. This allows
more flexible memory allocation because you can allocate memory based on the needs of the
program. An example of dynamic memory allocation:
// dynamically allocate the memory at runtime. This will contain garbage values
int *ptr = (int*) malloc(5 * sizeof(int));
You can also use realloc to change the size of an already allocated memory section:
You always have to use free after you have allocated memory and are done with it:
free(ptr);
ptr = NULL;
Memory segments
C memory is divided into multiple segments where you can allocate memory.
Stack:
Global/Static memory:
Variables declared outside of any function and variables using the static keyword
// if we do not free the `ptr` variable this will cause a memory leak.
int* ptr = (int*) malloc(10 * sizeof(int));
Dangling pointers
A dangling pointer is when a declared pointer points to memory that has already been
freed.
// create a pointer
int *ptr = (int*) malloc(sizeof(int));
Double free
A double free is when free() is used more than once on the same pointer
// create a pointer
int *ptr = (int*) malloc(sizeof(int));
// free it again
// this will cause undefined behavior, or crashes
free(ptr);
Wild pointers
A wild pointer is an uninitialized pointer. This means if you use a pointer before it has been
assigned valid memory.
int *p = (int*)
No (contains
malloc() Allocates memory dynamically. malloc(10 *
garbage values).
sizeof(int));
int *p = (int*)
Allocates memory for an Yes (initialized
calloc() calloc(10,
array and initializes to zero. to 0).
sizeof(int));
Frees dynamically
free() N/A free(p);
allocated memory.
Preprocessor directives
These are lines in the code that start with # . They are instructions to the preprocessor that run
before the compiler. Common types include:
#include
Include a source file or header file. Header files are either standard C library or a user
defined file.
// SYNTAX: #include <header_file>
#define
// defining a constant
// SYNTAX: #define CONSTANT_NAME value
#define PI 3.14159
#define MAX_SIZE 100
// defining a macro
// SYNTAX: #define MACRO_NAME(params) expression
#define SQR(x) ((x) * (x))
#undef
Undefine a macro. This macro must have already been defined using #define .
ifdef / ifndef
Conditional compilation based on whether a macro is defined or not. These are useful for
platform specific code/debugging.
Conditional compilation based on conditions. Allows more control over #define . Works
like an if/else statement
#if CONDITION
// perform action
#elif OTHER_CONDITION
// perform other action
#else
// perform another action
#endif
#pragma
Special compiler instructions. Behavior varies depending on compilers, is used mostly for
optimization, warnings, or other compiler specific features.
#define SQR(x) (x * x)
// this will expand to 3+1 * 3+1 which is not what we are looking for
printf("%d", SQR(3+1));
#define DOUBLE(x) (x + x)
int a = 5;
// this expands to a++ + a++ which modifies the variable twice
printf("%d", DOUBLE(a++));
Debugging difficulty:
Since macros are replaced before compilation debugging issues related to macros can
become extremely challenging.
Directive/Macro Description
#if / #elif /
Conditional compilation based on specific conditions.
#else / #endif
Debugging in C
Debugging is the process of identifying and fixing bugs/errors in a program. There are a lot of
debugging tools, and you should use what you want. We will not be going into all of them in this
course but will be focusing on the basic examples. Some common techniques include:
Print statements
This is by far the simplest method, you put print statements where you think the bug is and
see what data is being passed to it. This allows you to print potential problem values and
variables.
GDB debugger
The GNU debugger is a powerful tool that allows you to inspect your C code execution, set
breakpoints, step through the code, examine variable values, and track the cause of
runtime errors
Modern C compilers provide warnings that may catch potential issues at compile time.
Such as unused variables, incorrect types, possible zero division, etc. You should always
enable compiler warnings when compiling your program.
Static analysis tools
Tools like lint analyze source code for possible errors, suspicious constructs, and
coding standard violations. These tools are useful for detecting before compiling.
Error handling
Error handling is done manually in C. It is done by checking return values of functions using error
codes and handling the cases of the codes. C lacks built in error handling like try/catch blocks.
#include <stdio.h>
int main() {
// open a file and assign it to a pointer named `fh`
FILE *fh = fopen("filename.txt", "r");
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
// open a file and assign it to the `fh` pointer
FILE *fh = fopen("filename.txt", "r");
Aspect Description
Print Statements Simple debugging method by printing variable values and program state.
Static Analysis Tools Tools like lint help detect bugs and improve code quality.
Return Value Checking Manually check function return values to detect errors (e.g., fopen() ).
errno and
Provides detailed error reporting for certain standard library functions.
strerror()
The code
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
// these header files are standard C library
// stdio: includes things like printf and scanf
// stdlib: includes things like malloc and free
// errno: includes error codes for functions like fopen
// string: includes things like strerror for human readable error messages
// main function
int main() {
// handle any errors that might arise while doing so by checking if the size is withi
if (size < 0 || size > MAX_ARRAY_SIZE) {
logError("Invalid array size");
printf("Error: Invalid array size\n");
return 1;
}
// step #2: allocate the memory of the array automatically using the allocateArray fu
if (allocateArray(&arr, size) != 0) {
// exit on failure
printf("Memory allocation failed\n");
return 1;
}
// step #3: fill the array with values from the user
printf("Enter %d integer values:\n", size);
for (int i = 0; i < size; i++) {
scanf("%d", &arr[i]);
}
// step #4: print the numbers that the user entered for debugging
printf("You entered: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
// add a new line after we have printed the entered numbers out
printf("\n");
// return a success
return 0;
}
/*
create a pointer for the array
it is worth noting that you do not actually need to
cast malloc here since it will be a *void now
however due to the content and the target audience of
this course we will be casting it here
// return success
return 0;
}
Now you will need to save and compile this file like so:
What did we just do? Well, we used the gcc compiler in order to compile the C file into an
executable that we can call after compilation is finished. We used the -Wall to show all
That's all!
Now you have finished the course! We hope you got enough out of this to start writing in C and
be confident in what you're doing. We have taught you the bare basics of C and how it works.
This course is designed to provide beginners the information they need in order to start writing C
code. Please keep in mind:
This course is given to you for free by the Malcore team: https://m4lc.io/course/c/register
Consider registering, and using Malcore, so we can continue to provide free content for the
entire community. You can also join our Discord server here: https://m4lc.io/course/c/discord
We offer free threat intel in our Discord via our custom designed Discord bot. Join the Discord to
discuss this course in further detail or to ask questions.
Previous
Introduction to Python
Next
Introduction to File Types
Register
Pricing
Merch