1/40
Looks like no tags are added yet.
Name | Mastery | Learn | Test | Matching | Spaced |
---|
No study sessions yet.
C program structure
Preprocessor directives: Lines beginning with # used for including libraries or defining macros.
Function definitions: Including the main function and any other functions.
Statements and expressions: the actual code that performs operations.
What is C #include Preprocessor Directive?
Used to include the contents of a file within another file before the compilation begins. These header files usualy contain prototypes, macros and type definitions.
example:
#include <stdio.h>
or
#include “myheader.h”
How does #include work?
Preprocessing Stage: When the preprocessor encounters the include, it replaces that directive with the contents of the specified file. This happens before compilation.
Inclusion of Content: The compiler then processes this content as if it were part of the original source file, including prototypes, macro definitions, and type definitions.
Avoiding Multiple Inclusions: Since the included files are copied into the source file; we need to prevent including more than once, we use guards to do this.
How do we avoid Multiple Inclusions?
// checks if the file has not been defined
#ifndef MYHEADER_H
#define MYHEADER_H
// declarations
#endif
What is the “main” function?
Entry point of the program, when executed, main is the first function called.
It has two prototypes:
int main(int args, char* argv[]);
and
int main();
The first accepts command line arguements. In both cases, the function returns an int at the end of program execution. Returning a 0 means the program terminated successfully, any other indicates an error.
“Main” function example
#include <stdio.h>
int main() {
printf(“Hello World.\n”);
return 0;
}
What are escape sequences?
Escape sequences are a combination of a backslash (\) and another character.
\n moves the cursor to the begining of the next line.
\t moves the cursor to the next horizontal tab stop
\a produces an alert without changing the cursor position
\\ to input the backslash character in a string
\” to input the double quote character in a string
Variables
Must be declared before they are used, can be placed anywhere in the code before first use. C is case sensitive with variable names. They can be initialized at the same time.`
int index; // integers
char letter; // single chars
double distance // double precision floating point #
float temp; // single precision floating point #
Variable Scope and Lifetime
Global: declared outside any function or block, visible through the entire file.
Local: declared inside a function or block (eg. loops, conditional statements), limited to the function or block. only exists during the execution of the function or block, allocated when entered and deallocated when exited.
Data Types
int (Integer): 4 bytes (32 bits), represent whole numbers
char (Character): 1 byte (8 bits), represents a single character
float (single precision): 4 bytes (32 bits), represents real numbers with a fractional part
double (double precision): 8 bytes (64 bits), more precision than float.
Derived Data Types
void: absence of type, used for functions that do not return a value
array: a collection of elements of the same type
int numbers[5] = {1, 2, 3, 4, 5}
pointer: holds the memory address of another variable, typically 4 bytes on 32-bit system and 8 bytes on a 64bit system.
int value = 10;
int *ptr = &value;
signed and unsigned: modifiers that affect the range of values. Unsigned only allows positive numbers.
unsigned int positiveNumber = 10;
short and long: modifiers that adjust the size of the int type, short uses 2 bytes and long can use 8 bytes.
const keyword: defines a variable that cannot be changed after initialization.
What is a Macro?
The #define directive is used to define macros or constants at the preprocessing stage. I performs a search and replace in the code before the compilation begins.
#define NAME value
#define NUM_STUDENTS 500
does not occupy space in memory, just a literal with no type associated.
scanf function
Reads input from the standard input (keyboard), and stores the values in the provided variables. The format string controls how the input is parsed and interpreted
scanf(“control string”, &var1, &var2, …)
eg:
int marks;
float term;
scanf("%d:%f", &marks, &term);
char str[50];
scanf("%s", str);
any leftover characters or numbers not specified to match the type of the control string are still in the buffer.
printf function
outputs formatted data to the standard output (console). The format string specifies how the data should be formated and displayed.
printf(“control string”, var1, var2, …)
eg:
float flo = 3.14159;
printf("%f", flo);
%4d means field width of 4, right justified, padded with spaces
%4.1f means field width 4, 1 digit after the decimal point
%04d means field width of 4, padded with 0’s
(-) for left alignment
Control flow: Conditionals
if statement: checks whether condition is true or not.
else statement: if the condition in if is false, execute the code.
syntax:
if(<condition>) {
<code>
} else if(<another condition>) {
<code>
} else {
<code>
}
alternatively, switch case to select from mutually exclusive conditions:
syntax:
switch (<expression>) {
case const-expr: statements; break;
case const-expr: statements; break;
default: statements; break;
Control flow: Iteration
loops are used to repeat a block of code multiple times.
for loops used when number of iterations known.
while loops repeats while a condition is true.
do while loops guarantees one pass of the code block before checking the condition.
syntax:
for (i = 0; i < num; i++) {
<code>
}
while (!exitCondition) {
<code>
}
do {
<code>
} while (!exitCondition);
Logical operators
usually used in conditional statements
&& : logical AND
II : logical OR
! : logical NOT
relational operators:
== : equal
< : less than
> : greater than
!= : not equal
<= : less than or equal
>= : greater than or equal
Ternary operator
shortcut for an if-else statement:
<variable>= condition ? <if true code> : <if false code>;
Functions
The building blocks of code, used to break larger code into small pieces so that each can be maintained seperately, have reusable and readable code.
syntax:
<return type> functionName (arguments) {
}
the function declaration, known also as a function prototype, specifies the parameters without providing the implementation. It is best practice to include function prototypes at the start of the file.
Function Prototypes - Mixed Type Expressions
Arithmetic conversions in mixed-type expressions:
- if any operand is of type long double, all operands are converted to this type.
- next if any are of type double, all are converted to double.
- integer promotion ensures that smaller integer types (char and short) are promoted to int before evaluation
Arrays
fundemental data structure, used to store a collection of elements of the same type.
syntax:
int numbers[5];
int numbers[5] = {1, 2 ,3 ,4 ,5};
int numbers[] = {1, 2 ,3 ,4 ,5}; // size is inferred as 5
When you define an array in C, you reserve space for the array and its elements. the variable for the array itself is a pointer to the start of the array. (4 or 8 bytes, depending on architecture 32bit or 64bit)
it then allocates space for array elements equal to the number of elements times the size of one element.
In memory, arrays are contiguous blocks.
Variable Length Arrays (VLAs)
arrays are declared using a variable to specify the size of the array. syntax:
int n = 5;
int arr[n];
they can also be used as function parameters:
void processArray(int size, int arr[size])
{ // Process array arr
with size
}
int main() {
int n = 10;
int arr[n];
processArray(n, arr);
return 0;
}
They are dynamically sized, and determined at runtime. They are memory efficient, and they are flexible.
VLAs only appear in C99 standard or later. they are allocated on the stack, which can lead to stack overflow if the arrays are too large. The sizes aren’t checked at compile time, which means errors related to out-of-bounds access are tricky.
Char Array
Character arrays can be initialized with individual character constants:
char string1[] = {'f', 'i', 'r', 's', 't', '\0'}; // Equivalent to "first“
char string1[] = "first"; // Automatically adds '\0' at the end
A string is just an array of characters.
fgets (handling special characters)
scanf stops reading at whitespace (space, tab, newline). fgets is used to read from a file or stdin safely. syntax:
char *fgets(char *str, int n, FILE *stream);
*str: pointer to the character array where the string will be stored
n: maximum number of characters to read, including null terminator. (n-1 characters read)
stream: the input file stream to read from, usually stdin
returns the str pointer if a string is successfully read. returns NULL otherwise.
Multi-Dimensional Arrays
int matrix[2][3] = { // 2 rows and 3 columns
{1, 2, 3},
{4, 5, 6}
};
int value = matrix[1][2]; // Gets the value 6
Stack
The stack is used for static memory allocation, which includes function call management, local variables and function parameters.
Memory is allocated and deallocated automatically as functions are called and return.
Fixed size, determined by system or compiler settings. The memory is automatically managed.
Fast access (push/pop)
Limitation is that the stack has a limited size, and deep recursion or large local variables can lead to stack overflow.
Heap
Used for dynamic memory allocation, which allows you to allocate memory during runtime. Memory in C on the heap must be explicitly allocated and deallocated using functions like malloc, calloc, realloc and free.
Failure to free allocated memory leads to memory leaks.
Slower Access
Memory allocated on the heap is accessible from anywhere in the program provided you have a pointer to it.
int *ptr = (int *)malloc(sizeof(int)); // Allocate memory on the heap
if (ptr != NULL)
{
*ptr = 10; // Use the allocated memory
free(ptr); // Free the allocated memory
}
Stack Frames
The Stack is last in, first out (LIFO).
Each time a function is called, a stack frame is created and pushed onto the stack. the stack frame contains return address and local variables.
When a function completes, its frame is popped off, and control transfers to the address stored in that frame.
The stack has a finite size, meaning there is a limit to the number of how many stack frames can exist simultaneously.
Heap Allocation
As you implicitly or explicitly allocate memory, you are given the pointer to the best chunk (closest in size), and the heap table is updated.
If the amount needed matches an existing free chunk, it is returned to you and the chunk is marked as in use.
Otherwise, a bit of an existing free chunk is split off as a new chunk, this new chunk’s address is returned to you and is marked in use.
Heap Freeing (Deallocation)
When you free the allocated memory, the chunk in the heap table is marked as free.
Then if the location is adjacent to another free chunk, the location and size of both chunks are merged to create one large chunk. If there is no adjacent free chunk, it is left as is.
There is no Java-style garbage collection.
Stack Pitfalls
Data overruns are a risk. C will let you write data beyond the end of any array. You can then overwrite the return address of a function, which is a stack clobber error.
Stack overflow is a possibility. Endless recursion will consume all memory, or cause the stack pointer to enter an allocated chunk.
Stack/Heap collision, since they start on two ends of memory addresses.
Heap Pitfalls
You are responsible for freeing all the memory allocated. Memory leaks are a problem, requiring discipline and comments.
Fragmentation is a possibility. You can riddle the heap with alternating sequences of free and in-use chunks, so even though the sum of free chunks is enough, there are not enough adjacent free chunks.
Data overruns are a risk, C will let you write data beyond the end of your allocated chunk, corrupting data in the adjacent chunk. Very hard to debug.
malloc()
allocates a specified number of bytes of memory and returns a pointer to the beginning of the block, or Null if the allocation fails.
the parameter: size - number of bytes to allocate
returns: a pointer to the allocated memory or NULL
int *arr = (int *)malloc(5 * sizeof(int)); //Allocate memory for 5 integers
if (arr == NULL)
{
// Handle memory allocation failure
}
calloc()
allocates a specified number of bytes in memory and returns a pointer to the begining of the block, also initializes all memory elements to a value of zero.
the parameters: num - number of elements to allocate
size - size of each element in bytes
returns: a pointer to the allocated memory or NULL
int *arr = (int *)calloc(5, sizeof(int));
// Allocate memory for 5 integers initialized to 0
if (arr == NULL)
{
// Handle memory allocation failure
}
realloc()
changes the size of a previously allocated memory block.
parameters: *ptr - pointer to the previously allocated memory block
size - new size in bytes
return: a pointer to the newly allocated memory or NULL
arr = (int *)realloc(arr, 10 * sizeof(int)); /*Resize memory to hold 10 integers*/
if (arr == NULL) {
/*Handle memory reallocation failure*/
}
free()
Deallocates memory that was allocated.
parameters: *ptr - pointer to the memory to be freed
returns: nothing
free(arr);
arr = NULL; // set the pointer to NULL since there is no memory here anymore
Pointers
Pointers are a powerful feature that allows you to directly manipulate memory addresses. A pointer is a variable that stores the address of another variable. the type of pointer corresponds to the type of variable it points to.
To declare a pointer, use an asterisk (*).
int *ptr; // pointer to an int
You can initialize a pointer to point to a variable, (&) returns the memory address of a variable:
int var = 5;
int *ptr = &var;
Derefferencing, you can access the value stored in the address of the pointer by dereferrencing the pointer with an asterisk:
int value = *ptr; // value is now 5
Pointer Arithmetic
You can perform arithmetic on pointers. for example if ptr points to an integer, incrementing ptr will move it to point to the next integer in memory.
You can have NULL pointers (not assigned an address), best practice to assign any freed or not immediately assigned pointers to NULL.
You can have pointers to pointers, and const pointers.
Pointers vs Arrays
Both pointers and arrays can be used to access elements in memory. however arrays cannot be incremented or decremented.
An array is of fixed size at compile time, but a pointer can point to any memory address and can be reassigned.
The distinction between array and pointer is a semantic one, and pointers can be used to represent arrays.
Passing by Reference
Passing memory location between functions removes the need to maintain multiple copies of the same data, and allows it to be manipulated within other functions.
passing by reference can improve performance for large data structures, copying large arrays is time consuming, while passing a pointer is constant time O(1).
You can directly modify data of a passed pointer, meaning you don’t need to return values or copy data back.
Arrays of Pointers