System Memorywith C
System Memorywith C
While Python always reads from top to bottom, C always starts at the main()
function.
the asterik proppends to the variable name and the value is marked out
but double quotes ("...")
this is opposed to a simple char variable which uses single quotes
('x')
The void function signature is used to state that a function has no arguments
and when a function returns nothing:
int get_intefer(void){
return 42;}
Type sizes in C are not guaranteed, and can vary based on the system's
architecture. For example, on a 32-bit system, int is usually 4 bytes, while int on
a 64-bit system is 8 bytes.
The sizeof command in C allows you to check the size of a type or variable.
Initialization
Condition
Final-expression.
the Initialization is only executed at the beginning of the loop once, and is
typically used to initialize a loop coutner like int i = 0.
The condition is checked before each interation. If true the body executes,
if false the loop terminates. often checks if i is less than some value
The final expression is executed after each loop, and can be used to update
the loop counter for example.
Unlike the while loop, the do while loop checks the condition after executing the
loop body, so the loop body is always executed at least once.
Syntax
do {
// Loop Body
} while (condition);
.h (header) files can cause issues if the compiler tries to read it more then once.
This can be prevented adding #pragma once to the top of the header file, which
tells the compiler to include the file only once, even if it is referenced multiple
times across the program.
They also solve the issue where a function cannot return more than one value:
where a return 3 seperate values in another language, C returns a
single struct containing those values
int main() {
int age = 37;
printf("The address of age is: %p\n", &age);
return 0;
}
Arrays canot be iterated over using a for x in list syntax, and must be
iterated over using a for loop (or other conditional loop) via an index:
#include <stdio.h>
int main(){
int numbers[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++){
printf("%d ", number[i]);
}
printf("\n"');
return 0;
}
Pointers will always be the same size on a single system, since they are just
memory address.
Arrays can be different in size since the number and type of elements can vary, and
allocate memory for all their elements.
Arrays can decay into pointers, where the array becomes just a pointer to its
first element.
int arr[5];
int *ptr = arr; // 'arr' decays to 'int*'
int value = *(arr + 2); // 'arr' decays to 'int*'
This is way an array can't be passed to a function by value, since the array
name decays to a pointer.
hence array casting
Forward Declaration is used for a struct that needs to reference itself or be used
recursivelt.
Below, the node needs to reference itself (node_t * next), but node_t hasn't
been defined yet.
typedef struct Node {
int value;
node_t *next;
}node_t;
This can be resolved using a forward declaration:
typedef struct Node node_t;
typedef struct Node {
int value;
node_t *next;
} node_t;
To change the default value (useful for error codes, for example) include =
VALUE in the definition as with default arguments:
typedef enum {
EXIT_SUCCESS = 0,
EXIT_FAILURE = 1,
EXIT_COMMAND_NOT_FOUND = 127,
} ExitStatus;
If you assign a default value to one entry, the following entires will be
incremented from it:
typedef enum {
LANE_WPM = 200,
PRIME_WPM, // 201
TEEJ_WPM, // 202
} WordsPerMinute;
Switch Case:
A special use for enums:
switch (logLevel) {
case LOG_DEBUG:
printf("Debug logging enabled\n");
break;
case LOG_INFO:
printf("Info logging enabled\n");
break;
case LOG_WARN:
printf("Warning logging enabled\n");
break;
case LOG_ERROR:
printf("Error logging enabled\n");
break;
default:
printf("Unknown log level: %d\n", logLevel);
break;
}
Sizeof Enum:
Generally they are the same size as int; however if the value of an enum
exceeds the size of int, the C compiler will use a larger integer type,
such as unsigned int or long.
the above can hold either an int or a char, but not both (which a struct can
do). What the union does is provide a list of possible types so that the C compiler
knows the maximum possible memory requirement and can account for that.
They are used like:
age_or_name_t lane = { .age = 29 };
printf("age: %d\n", lane.age);
// age: 29
When called, the stack pointer is moved from the lowest address to make room
for:
the return address, arguments to the function, and local variables in
the function body.
The local variables are stored in the stack frame
When the function is returned, the stack frame is reallocated by
resetting the stack pointer where the frame began
Stack Overflow:
The stack has a limited size- if that size is exceeded, you get a stack
overflow. This is why recursion w/o tail-call optimization is dangerious.
If you have too many recursive calls, you will run out of stack
space.
The Heap:
The Heap is slower and more complex than the stack, but allows more
complex data structures.
Since C needs to know how where to put data, and how large that data
is, the Heap is useful for when you don't know how large that data is going to be
at time of writing.
*Arguments are copied into the stack.
The memory assigned to the heap is dynamically allocated and is not
freed automatically (potentially causing memory leaks)
The Stack is known ahead of time and can only exist within one
function
The Heap is for when a size is unknown or when return value isn't
limited to one function.
Casting to and from void pointers in C is unique because void pointers are
type-agnostic. When casting a specific type pointer to a void pointer, no type
information is retained, allowing the void pointer to point to any data type.
However, you must cast a void pointer back to its original type before
dereferencing it, as direct dereferencing is not possible.