# Week 1 - Memory
## Team
Team name: Gappies van andere wappies
Date: 09-02-2022
Members:
Stephan Windemuller
Stijn Stroeve
Ingmar Neple
| Role | Name |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------|
| **Facilitator** keeps track of time, assigns tasks and makes sure all the group members are heard and that decisions are agreed upon. |Stephan|
| **Spokesperson** communicateVVs group’s questions and problems to the teacher and talks to other teams; presents the group’s findings. |Ingmar|
| **Reflector** observes and assesses the interactions and performance among team members. Provides positive feedback and intervenes with suggestions to improve groups’ processes. |Ingmar|
| **Recorder** guides consensus building in the group by recording answers to questions. Collects important information and data. |Stijn|
## Activities
Make sure to have the activities signed off regularly to ensure progress is tracked.
Set up a project in CLion to write the small programs needed in some of the activities.
### Activity 1: Memory usage - the sizeof operator
|data type| size|
|---------|-----|
|char| 1|
|short int| 2|
|int| 4|
|long int| 8|
|long long int| 8|
|float| 4|
|double| 8|
|long double| 16|
|char pointer| 8|
|int pointer| 8|
|float pointer| 8|
|double pointer| 8|
|void pointer| 8|
The size of the pointer does not change between data types, because a pointer is always a long int.
### Activity 2: Array and structure sizes
This is how you include code listings in your markdown document:
```C
typedef struct {
char name[80];
int age;
} person_t;
void size_array(int array[]) {
printf("Size of array parameter: %lu\n", sizeof(array));
}
void size_struct(person_t p) {
printf("Size of p parameter: %lu\n", sizeof(p));
}
int main() {
int array[10];
person_t bob = {.name = "Bob", .age = 22};
printf("Size of int array: %lu\n", sizeof(array));
printf("Size of person_t structure: %lu\n", sizeof(bob));
size_array(array);
size_struct(bob);
}
```
Size of int array when initialized is 10 * 4 bytes
For the function the reference to the array, so the pointer, is passed
Size of person struct is predefined, so the same for the struct initialization and an instance of the struct
### Activity 3: Memory addresses
```C
int main( void ) {
int a = 0xA0B0C0D0;
short int b = 0x7856;
const char * s = "Hello!";
char buf[] = "Pointer";
short int c = 0x3412;
printf("int a: %p\n", (void*) &a);
printf("short int b: %p\n", (void*) &b);
printf("const char *s: %p\n", (void*) &s);
printf("char buf[]: %p\n", (void*) buf);
printf("short int c: %p\n", (void*) &c);
}
```
The characters of s are not stored near other variables.
The other variables are laid out in a contiguous way in memory.
### Activity 4: Observing automatic lifetime
```C
int add(int a, int b) {
int c = a + b;
printf("Memory address a: %p\n", &a);
printf("Memory address b: %p\n", &b);
printf("Memory address c: %p\n", &c);
return c;
}
int mul(int x, int y) {
int z = x * y;
printf("Memory address x: %p\n", &x);
printf("Memory address y: %p\n", &y);
printf("Memory address z: %p\n", &z);
return z;
}
int main( void ) {
printf("%d\n", mul(add(3, 4), add(1, 5)));
}
```
This code logs the following:
```
Memory address a: 0x7ff7b9d3992c
Memory address b: 0x7ff7b9d39928
Memory address c: 0x7ff7b9d39924
Memory address a: 0x7ff7b9d3992c
Memory address b: 0x7ff7b9d39928
Memory address c: 0x7ff7b9d39924
Memory address x: 0x7ff7b9d3992c
Memory address y: 0x7ff7b9d39928
Memory address z: 0x7ff7b9d39924
42
```
As you can see in the logs above, the memory addresses are reused for every function. Even the addresses of the local variables in the `mul` are reused.
When the first call of the `add` function is d
ne the memory is not needed anymore so it is freed up. Because the memory is n#w empty it can be used again in the nex# `add` function call. After this memory is cleaned again it can be used by the `mul` function. # Activity 5: Observing the stack
### Activity 5: Observing the stack
```C
int poly(int a) {
int b = a * (a + 1);
printf("%p\n" , &a);
printf("%p\n" , &b);
return b / 2;
}
int add_polys(int x, int y) {
int bx = poly(x);
int by = poly(y);
return bx + by;
}
int main(void) {
printf("%d\n", add_polys(42, 24));
}
```
The first 42 is sent to poly, then it has an alocated memory (0x309f14a8c), once the function of 'poly' is done the memory address of 42 can be reused. This is done when poly is called for the number 24 which will given the same memory address.
### Activity 6: Leaking local addresses
Because the `get_answer` function moves out of scope after calling it once, the memory address becomes free again. The `i_do_nothing` function requests a memory address, and is assigned the old one of the `answer` variable, thus changing the value at that pointer from 42 to 24.
### Activity 7: Memory addresses of local variables
- warning: function returns address of local variable [-Wreturn-local-addr] means the function returns a pointer to a value that only exists in the context of that function, and ceases to exist when the function moves out of scope
- error: undefined reference to `do_some_work', this error happens because the "do_some_work" function does not have a body, but is called.
- This approach to create an array at runtime does not work, since the pointer to the first element does not point anywhere useful, since the array only exists in the scope of the function
### Activity 8: Using malloc
```c
#include <stdlib.h>
int* allocate_memory(int count) {
int* ret_ptr;
ret_ptr = (int*) malloc(count * sizeof(int));
return ret_ptr;
}
int main() {
unsigned long *u_long_ptr;
u_long_ptr = (unsigned long*) malloc(sizeof(unsigned long));
float *float_ptr;
float_ptr = (float*) malloc(256 * sizeof(float));
int* int_ptr = allocate_memory(2);
return 0;
}
```
### Activity 9: Using allocated memory as an array
Questions:
- How many int elements can be stored in the allocated block of memory?
*Answer*: There are 20 bytes allocated, so if each int takes up 4 bytes the formula is 20/4 = 5. So 5 integers can be stored.
- What happens when you perform an out-of-bounds access to an array that is stored in dynamically allocated memory?
*Answer*: It can cause undefined behaviour. You don't know what is going to happen.
- What is the problem in the program listed below, and how can it be fixed?
*Answer*: The program with the program is that too little memory is allocated for the array. If there need to be 20 integers in the array we need to allocate 20 * size of an integer. I fixed that in the program below.
Fixed program:
```c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const int capacity = 20;
int *ptr = (int*) malloc(capacity * sizeof(int));
for (int i = 0; i < capacity; i++) {
printf("ptr[%d] = %d\n", i, ptr[i]);
}
}
```
### Activity 10: Infinite memory?
```c
int main(void) {
const unsigned int block_size = 1 << 28;
int t = 0;
void *ptr = malloc(block_size);
while (ptr != NULL) {
ptr = malloc(block_size);
printf("Bytes allocated: %d\n", ++t * block_size);
}
```
// the program allocates a certain amount of bytes until the maximum of which the program is capable is reached. We tested this on multiple computers and stopped allocating at the same point
### Activity 11: Fixing a memory leak
int main(void) {
const unsigned int block_size = 1 << 28;
void *ptr = malloc(block_size);
unsigned long long bytes_allocated = 0;
while (ptr != NULL) {
ptr = malloc(block_size);
bytes_allocated += block_size;
printf("Bytes allocated: %lld\n", bytes_allocated);
free(ptr);
}
// by using the free function the memory gets freed, this way the program can keep running for a long time.
### Activity 12: Dangerous `free`s
When you execute free on a pointer that was not obtained by malloc the program seems to crash.
When you execute free on a null pointerm no operation is performed. According to https://linux.die.net/man/3/free. And when we only execute free on a null pointer, the program does not crash and just does nothing.
```c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int stack_variable = 42;
int* ptr = &stack_variable;
free(ptr);
int* null_ptr = NULL;
free(null_ptr);
}
```
### Activity 13: Using realloc
1 call, since the memory keeps being reused by realloc.
### Activity 14: Using a dynamically sized buffer
Fixed program:
```c
#include <stdio.h>
#include <stdlib.h>
#define CAPACITY 20
int main(void) {
char *ptr = NULL;
ptr = realloc(ptr, sizeof(char[CAPACITY]));
if (!ptr){
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
FILE *file = fopen("input.txt", "r");
if (!file) {
fprintf(stderr, "Error opening file\n");
return 1;
}
unsigned long capacity = CAPACITY;
unsigned long count = 0;
int c = fgetc(file);
while (c != EOF) {
if(count == capacity) {
// In the example it said we should increase the capacity by 1.5 times by that could convert long to double. So we do 2 times instead.
capacity *= 2;
ptr = realloc(ptr, capacity * sizeof(char));
}
ptr[count++] = (char) c;
c = fgetc(file);
}
}
```
## Looking back
### What we've learnt
- Using the sizeof operator
- Not leaking local memory addresses
- Using malloc/realloc/free
- Solving memory leaks
- In general: using Dynamical memory
### What were the surprises
- That you need to organise the memory a lot yourself in C
### What problems we've encountered
- Local variables get deleted
- C is memory unsafe
- The amount of exercises
- How the exercises were formulated.
- Different compilers return different results
### What was or still is unclear
- Nothing is really unclear at this point. As we have asked the teachers all our questions.
### How did the group perform?
- Good task distribution
- Good communication
- Bad planning