0% found this document useful (0 votes)
21 views8 pages

System Memorywith C

The document provides a comprehensive overview of C programming concepts, including memory management, data types, functions, loops, structs, pointers, arrays, enums, unions, and memory allocation. It highlights key differences between C and Python, such as variable declaration, type safety, and memory handling. Additionally, it covers best practices for using header files and managing memory to prevent leaks and ensure efficient program execution.

Uploaded by

John Irving
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views8 pages

System Memorywith C

The document provides a comprehensive overview of C programming concepts, including memory management, data types, functions, loops, structs, pointers, arrays, enums, unions, and memory allocation. It highlights key differences between C and Python, such as variable declaration, type safety, and memory handling. Additionally, it covers best practices for using header files and managing memory to prevent leaks and ensure efficient program execution.

Uploaded by

John Irving
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 8

System Memory and C

While Python always reads from top to bottom, C always starts at the main()
function.

As C is compiled, a crash will occure before the program even starts. It


rarely gives much information about what caused the crash.
in c, all variables mush have a defined data type (i.e. int, float, char,
char *)

char * represents an array (or list) of characters, and is broadly equivalent


of a python string.
char *msg_from_dax = "You still have 0 users";

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')

In C a variables type CANNOT be changed, although it's value can.


When changing a variable's value, do not declare its type again.

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;}

void print_integer(int x){


printf("this is an int: %d", x);
}

µnit: A lightweight test module for C.


tests/prototypes for C are in a .h file; ex) exercise.c is the "real"
file, but exercise.h is the prototype.

In C, incrementing/decrementing a variable is handled via "++" and "--"


these can be postfixed or prefixed. When postfixed, the variable
is handled after incrementing/decrementing, while the prefix holds the variable
before:
int a = 5;
int b = a++; //b is assigned 5, then a becomes 6
-------
int a = 5;
int b = ++a; // a becomes 6, then b is assigned 6

C has a ternary operator, like JS:


int a = 5;
int b = 10;
int max = a > b ? a : be
/* a > b is the condition
a is the final value if the condition is true,
b is the final value if false

Ternaries are a way to write simple if/else statements in one line

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.

The syntax of a for loop in C consists of three main parts:

Initialization
Condition
Final-expression.

There is no "for each" (iterables) in C. For example, there is no way to do:


for car in cars:
print(car)
Instead you must interate over the indices of a list

The C for-loop syntax is as follows:


for (initialization; condition; final-expression) {
// Loop Body
}

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.

The While loop follows the syntax below:


while (condition) {
// Loop Body
}
while the condition is true, the loop body executes.

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.

another way of preventing this is using header guards via preprocessor


directives.
#indef MY_HEADER_H
#define MY_HEADER_H
// code
#endif
this works by defining a unique macro for the header file, and if it has
already been included, the guard prevents it from being included again

#pragma once is generally preferred.


Structs:
superficially similar to Python classes, but simpler.
They are way of grouping multiple fields (or data points) and grouping them
together as an object.
Like objects, they have multiple fields but no methods, just data.
The order of the fields is important for purposes of memory.

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

To initialize a struct, you have a few different options.

The Zero Initializer sets all values to zero:


int main(){
stuct City c = {0};
}

The Positional initializer:


int main(){
struct City c = {"San Francisco", 37, -122}

The Designated Initializer:


int main(){
struct City c = {
.name = "San Francisco",
.lat = 37,
.lon -122}
The designated initializer is prefered.

The type def keyword allows an alias to be defined for structs:


struct Pastry {
char *name;
float weight;
};
can be defined as
typedef struct Pastry {
char *name;
float weight;
} pastry_t;
allows the struct to be called as pastry_t instead of struct Pastry.

use the address (&) operator to pass variables of a larger size:


#include <stdio.h>

int main() {
int age = 37;
printf("The address of age is: %p\n", &age);
return 0;
}

// The address of age is: 0xfff8


This pulls the physical address of the variable rather than it's value.
.h files are for declarations of types(like structs) and function prototypes

A pointer is a variable that holds a memory address of a variable, the variable


holding the actual data.
it is declared with an asterik after it's type:
int age = 37;
int *pointer_to_age = &age;

Pointers can be used to directly overwrite a value/struct's address, allowing


it to be updated without creating a new instance of that struct.

Fields of structs can be accessed throught pointers by:


a) using the arrow (->) operator:
coordinate point = {10, 20, 30}
coordinate_t *ptrToPoint = &point
printf("X: %d\n", prtToPoint->x)

b) deferenece and access the field in one step:


coordinate_t point = {10, 20, 30};
coordinate_t *ptrToPoint = &point;
printf("X: %d\n", (*ptrToPoint).x);
the . operator has a higher precedence than the * operator. The arrow
operator is prefered due to this.

C Arrays are like Python Lists, but at a lower level.


They are defined with a set length and can only hold values of a single type:
int numbers[5] = {1, 2, 3, 4, 5}

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;
}

Because arrays are essentially pointers, and structs are contiguous in


memory, they can be cast the array of structs to an array of integers

for (int i = 0; i < 9; i++) {


printf("points_start[%d] = %d\n", i, points_start[i]);
}
/*
points_start[0] = 5
points_start[1] = 4
points_start[2] = 1
points_start[3] = 7
points_start[4] = 3
points_start[5] = 2
points_start[6] = 9
points_start[7] = 6
points_start[8] = 8
*/

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

Arrays don't decay when:


sizeof operator: returns the size of the entire array, and not just the
pointer
& operator: yields a pointer to the entire array and not just the first
element
Initialization: when an array is first declared and initialized, it is
fully allocated and does not decay to a pointer.

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;

Enums are defined as:


typedef enum DaysOfWeek {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY,
} days_of_week_t;

by default, each entry will be assigned a digit based on its position,


starting from 0.

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;
}

Avoid "magic numbers"; use descriptive names

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.

Unions are a hybrid of structs and enums.


They can hold several types, acting like a less-strict sum type:
typedef union AgeOrName {
int age;
char *name;
} age_or_name_t;

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

if union is already assigned one type, trying to access another type


yields nothing :
printf("name: %s\n", lane.name);
// name:

Assigning another value overwrites the previous one.


Union values are stored in the same location in memory, no matter which
field/value is assigned or accessed.
Attempting to access a field apart the one originally set is
discouraged.

The size of a union is the size of it's largest possible field.

The Stack and the Heap:


Memory is broadly divided into two regions: the Stack and the heap.
The Sstack is where variables are stored. When a function is called a new
stack frame is crated in memory to store the function's parameters and local
variables.
When the function returns, the stack is cleared and reallocated.
The stack follows a "LAst in, First out"data structure, like a deck of cards.

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.

Malloc, or Memory Allocation allocaates the specified number of bytes of


memory on the heap and returns a pointer to the allocated memory.
The new memory is uninitialized, so it contains whatever data was
already written there; it is thus the programmers responsibility to ensure the
allocated memory is properly initialized via free() to prevent memory leaks.
To ensure the memory is properly initiated use the calloc function.

Function Signature: void* malloc(size_t size);


size is the number of bytes to allocate
It returns a pointer to the allocated memory or NULL if the
allocation fails

if the memory allocated with malloc isn't released with free(),


it will never be released.

Dynamic memory behaves as an array:


// Allocate memory
int *ptr;
ptr = calloc(4, sizeof(*ptr));

// Write to the memory


*ptr = 2;
ptr[1] = 4;
ptr[2] = 6;

Free() deallocates memory assigned with malloc, calloc, or realloc.


It does not purge it, it just tells the OS it can be rewritten.

Endianess: Refers to the order in which bytes are stored in memory.


Big Endian: The largest part(s) of the number are stored in the lowest
memory address.
Little Endian: The smalles part is stored first.

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.

int number = 42;


void *generic_ptr = &number;

// This doesn't work


printf("Value of number: %d\n", *generic_ptr);

// This works: Cast to appropriate type before dereferencing


printf("Value of number: %d\n", *(int*)generic_ptr);

You might also like