diff --git a/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/README.md b/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/README.md new file mode 100644 index 00000000..6850bc2b --- /dev/null +++ b/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/README.md @@ -0,0 +1,33 @@ +# Data Inspection + +You will solve the exercise starting from the `inspect.c` file located in the `drills/tasks/inspect/support` directory. + +Given the following declarations: + +```c +#include + +int main() { + unsigned int a = 4127; + int b = -27714; + short c = 1475; + int v[] = {0xCAFEBABE, 0xDEADBEEF, 0x0B00B135, 0xBAADF00D, 0xDEADC0DE}; + + unsigned int *int_ptr = (unsigned int *) &v; + + for (int i = 0 ; i < sizeof(v) / sizeof(*int_ptr) ; ++i) { + ++int_ptr; + } + + return 0; +} +``` + +Compile the source code and run the executable with GDB. +Set a breakpoint at `main` and observe how the data is represented in memory. +For this task, you will use the `print` and `examine` commands. + +> **NOTE:** +> +> - To display the value of a variable in hexadecimal, use `p/x variable_name` +> - To display the value from a pointer, use `p *pointer_name`, and to inspect the data at a memory address, use `x memory_address`. diff --git a/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/support/.gitignore b/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/support/.gitignore new file mode 100644 index 00000000..190d8e78 --- /dev/null +++ b/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/support/.gitignore @@ -0,0 +1 @@ +/inspect diff --git a/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/support/Makefile b/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/support/Makefile new file mode 100644 index 00000000..4b7455fb --- /dev/null +++ b/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/support/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -Wall -g +LDFLAGS = + +TARGET_EXEC = inspect + +SRCS := $(shell find $(SRC_DIRS) -name "*.c") +OBJS := $(SRCS:.c=.o) + +$(info OBJS is $(OBJS)) +$(info SRCS is $(SRCS)) + +$(TARGET_EXEC): $(OBJS) + $(CC) $(OBJS) -o $@ $(LDFLAGS) + +.o: %.c + $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@ + +.PHONY: clean + +clean: + $(RM) -r *.o $(TARGET_EXEC) diff --git a/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/support/inspect.c b/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/support/inspect.c new file mode 100644 index 00000000..63f21613 --- /dev/null +++ b/chapters/memory-layout/introduction-to-GDB/drills/tasks/inspect/support/inspect.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include + +int main(void) +{ + unsigned int a = 4127; + int b = -27714; + short c = 1475; + int v[] = {0xCAFEBABE, 0xDEADBEEF, 0x0B00B135, 0xBAADF00D, 0xDEADC0DE}; + + unsigned int *int_ptr = (unsigned int *) &v; + + for (unsigned int i = 0 ; i < sizeof(v) / sizeof(*int_ptr) ; ++i) + ++int_ptr; + + (void) a; + (void) b; + (void) c; + + return 0; +} diff --git a/chapters/memory-layout/introduction-to-GDB/guides/segfault/README.md b/chapters/memory-layout/introduction-to-GDB/guides/segfault/README.md new file mode 100644 index 00000000..099d4aff --- /dev/null +++ b/chapters/memory-layout/introduction-to-GDB/guides/segfault/README.md @@ -0,0 +1,44 @@ +# GDB Tutorial: Debugging a Segfault + +You will solve the exercise starting from the `segfault.c` file located in the `drills/tasks/segfault/support` directory. + +Compile and run the source code from the skeleton (if you are not using the Makefile, make sure to compile with the -g flag). +In short, the program takes a number n, allocates a vector of size n, and initializes it with the first n numbers from the Fibonacci sequence. +However, after running the program, you see: Segmentation fault (core dumped). + +Start GDB with the executable: + +```bash +gdb ./segfault +``` + +Once you have started GDB, all interaction happens through the GDB prompt. +Run the program using the `run` command. +What do you notice? +GDB hangs at the input reads. + +Set a breakpoint at `main` using the `break main` command. +You will see the message in the prompt: + +```c +Breakpoint 1 at 0x7d3: file seg.c, line 21 /* The memory address should not be the same */ +``` + +Next, we will step through the instructions one by one. +To do this, use the `next` or `n` command (watch the GDB cursor to see the current instruction and repeat the process). +You will notice that GDB hangs at `scanf`, so input a value for `n` and continue stepping through. +If you have entered a large value for `n` and want to skip the iteration, use the `continue` command. +Eventually, you will reach the line `v[423433] = 3;`, and GDB will display: + +```bash +Program received signal SIGSEGV, Segmentation fault +``` + +Inspect the memory at `v[423433]` using `x &v[423433]` and you will receive the message: + +```c +Cannot access memory at address 0x5555558f3e94 /* The memory address should not be the same */ +``` + +What happened? +We accessed a memory area with restricted access. diff --git a/chapters/memory-layout/introduction-to-GDB/guides/segfault/support/.gitignore b/chapters/memory-layout/introduction-to-GDB/guides/segfault/support/.gitignore new file mode 100644 index 00000000..efe4f639 --- /dev/null +++ b/chapters/memory-layout/introduction-to-GDB/guides/segfault/support/.gitignore @@ -0,0 +1 @@ +/segfault diff --git a/chapters/memory-layout/introduction-to-GDB/guides/segfault/support/Makefile b/chapters/memory-layout/introduction-to-GDB/guides/segfault/support/Makefile new file mode 100644 index 00000000..d1f200ac --- /dev/null +++ b/chapters/memory-layout/introduction-to-GDB/guides/segfault/support/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -Wall -g +LDFLAGS = + +TARGET_EXEC = segfault + +SRCS := $(shell find $(SRC_DIRS) -name "*.c") +OBJS := $(SRCS:.c=.o) + +$(info OBJS is $(OBJS)) +$(info SRCS is $(SRCS)) + +$(TARGET_EXEC): $(OBJS) + $(CC) $(OBJS) -o $@ $(LDFLAGS) + +.o: %.c + $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@ + +.PHONY: clean + +clean: + $(RM) -r *.o $(TARGET_EXEC) diff --git a/chapters/memory-layout/introduction-to-GDB/guides/segfault/support/segfault.c b/chapters/memory-layout/introduction-to-GDB/guides/segfault/support/segfault.c new file mode 100644 index 00000000..4cc27569 --- /dev/null +++ b/chapters/memory-layout/introduction-to-GDB/guides/segfault/support/segfault.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include + +int nth_fibo(const int n) +{ + if (n == 1 || n == 2) + return 1; + + int first = 1, second = 1, nth_fib = 0; + + for (int i = 3 ; i <= n ; ++i) { + nth_fib = first + second; + first = second; + second = nth_fib; + } + + return nth_fib; +} + +int main(void) +{ + int n; + + scanf("%d", &n); + int *v = malloc(n * sizeof(*v)); + + for (int i = 0 ; i < n; ++i) + v[i] = nth_fibo(i); + + v[423433] = 3; + free(v); + + return 0; +} diff --git a/chapters/memory-layout/introduction-to-GDB/reading/README.md b/chapters/memory-layout/introduction-to-GDB/reading/README.md new file mode 100644 index 00000000..94d493b9 --- /dev/null +++ b/chapters/memory-layout/introduction-to-GDB/reading/README.md @@ -0,0 +1,84 @@ +# GNU Debugger (GDB) + +## Starting GDB + +GDB is a powerful tool for debugging programs. +It allows you to inspect the state of a program at a certain point in its execution, set breakpoints, and step through the code, among other things. +To start GDB, you need to run the following command: + +```bash +gdb [program_name] +``` + +## Running the Program + +To run the program being debugged, there are two available commands: + +- `r` or `run` - this command will run the program +- `start` - unlike `run`, this command will start the program but immediately stop after entering `main` + +## Breakpoints + +The essential element of GDB is the breakpoint. +Essentially, setting a breakpoint at a certain instruction causes the program's execution to halt every time it reaches that point. +Setting a breakpoint is done with the following command: + +```gdb +break [location] +``` + +where *location* can represent the name of a function, the line number of the code, or even a memory address, in which case the address must be preceded by the symbol *. +For example: `break \*0xCAFEBABE` + +## Stepping through instructions + +- `si` or `stepi` - executes the current instruction +- `ni` or `nexti` - similar to `stepi`, but if the current instruction is a function call, the debugger will not enter the function +- `c` or `continue` - continues program execution until the next breakpoint or until it finishes +- `finish` - continues program execution until leaving the current function + +## Inspecting Memory + +- `p` or `print` var - displays the value of `var`. +Print is a very flexible command, allowing dereferencing of pointers, displaying addresses of variables, and indexing through arrays using *, & and []. +The print command can be followed by the /f parameter specifying the display format (x for hex, d for decimal, s for string). +- `x` or `examine` - Inspects the content at the given address. +The usage of this command is as follows: + +```text +x/nfu address +``` + +where: + +- n is the number of displayed elements +- f is the display format (x for hex, d for decimal, s for string, and i for instructions) +- u is the size of each element (b for 1 byte, h for 2, w for 4, and g for 8 bytes) + +We recommend the article [Debugging](https://ocw.cs.pub.ro/courses/programare/tutoriale/debugging) for further understanding of how to use GDB both in the CLI and through an IDE. + +## pwndbg + +[pwndbg](https://github.com/pwndbg/pwndbg) is a GDB plugin that provides a number of useful features for debugging and exploiting binaries. +It makes GDB easier to use and infinitely more powerful. +It will become more useful as we progress through the lab sessions. + +Cheatsheet [gdb + pwndbg](https://cheatography.com/superkojiman/cheat-sheets/gdb-pwndbg/); pwndbg [features](https://github.com/pwndbg/pwndbg/blob/dev/FEATURES.md) + +```pwndbg +pwndbg> show context-sections +'regs disasm code ghidra stack backtrace expressions' +# for smaller terminals +pwndbg> set context-sections 'regs code stack' +# display memory area in hex + ASCII +pwndbg> hexdump $ecx +# display stack +pwndbg> stack +# permanently display memory dump of 8 bytes +pwndbg> ctx-watch execute "x/8xb &msg" + +# recommended settings in .gdbinit +set context-sections 'regs code expressions' +set show-flags on +set dereference-limit 1 +``` diff --git a/chapters/memory-layout/memory-operations/demos/array-vs-pointer/.gitignore b/chapters/memory-layout/memory-operations/demos/array-vs-pointer/.gitignore new file mode 100644 index 00000000..dad0aae0 --- /dev/null +++ b/chapters/memory-layout/memory-operations/demos/array-vs-pointer/.gitignore @@ -0,0 +1 @@ +/array_vs_pointer diff --git a/chapters/memory-layout/memory-operations/demos/array-vs-pointer/Makefile b/chapters/memory-layout/memory-operations/demos/array-vs-pointer/Makefile new file mode 100644 index 00000000..3ce494d2 --- /dev/null +++ b/chapters/memory-layout/memory-operations/demos/array-vs-pointer/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -Wall -g +LDFLAGS = + +TARGET_EXEC = array_vs_pointer + +SRCS := $(shell find $(SRC_DIRS) -name "*.c") +OBJS := $(SRCS:.c=.o) + +$(info OBJS is $(OBJS)) +$(info SRCS is $(SRCS)) + +$(TARGET_EXEC): $(OBJS) + $(CC) $(OBJS) -o $@ $(LDFLAGS) + +.o: %.c + $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@ + +.PHONY: clean + +clean: + $(RM) -r *.o $(TARGET_EXEC) diff --git a/chapters/memory-layout/memory-operations/demos/array-vs-pointer/array_vs_pointer.c b/chapters/memory-layout/memory-operations/demos/array-vs-pointer/array_vs_pointer.c new file mode 100644 index 00000000..c8beabee --- /dev/null +++ b/chapters/memory-layout/memory-operations/demos/array-vs-pointer/array_vs_pointer.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include + +char v[] = "ana are mere"; +char *p = "ana are mere"; + +int main(void) +{ + printf("sizeof(v): %zu\n", sizeof(v)); + printf("sizeof(p): %zu\n", sizeof(p)); + + printf("v[1]: %c\n", v[1]); + printf("p[1]: %c\n", p[1]); + + v[1] = 'd'; + // p[1] = 'd'; /* this seg faults */ + + return 0; +} diff --git a/chapters/memory-layout/memory-operations/drills/tasks/delete_first/README.md b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/README.md new file mode 100644 index 00000000..0d1596c7 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/README.md @@ -0,0 +1,14 @@ +# Deleting the First Occurrence of a Pattern from a String + +You will solve the exercise starting from the `delete-first.c` file located in the `drills/tasks/delete-first/support` directory. + +Given a string and a pattern, implement the `delete_first(char *s, char *pattern)` function that returns the string obtained by removing the first occurrence of the pattern in `s`. + +> **NOTE:** For `s = "Ana are mere"` and `pattern = "re"`, the function should return the string "Ana a mere". +> +> **IMPORTANT:** Warning +> +> ```c +> char *s = "Ana are mere" // allocates the string in a read-only memory area (immutable content); +> char s[] = "Ana are mere" // allocates the string in a read-write memory area (modifiable content); +> ``` diff --git a/chapters/memory-layout/memory-operations/drills/tasks/delete_first/solution/.gitignore b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/solution/.gitignore new file mode 100644 index 00000000..f1c8fd35 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/solution/.gitignore @@ -0,0 +1 @@ +/delete_first diff --git a/chapters/memory-layout/memory-operations/drills/tasks/delete_first/solution/Makefile b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/solution/Makefile new file mode 120000 index 00000000..a39be4f2 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/solution/Makefile @@ -0,0 +1 @@ +../support/Makefile \ No newline at end of file diff --git a/chapters/memory-layout/memory-operations/drills/tasks/delete_first/solution/delete_first.c b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/solution/delete_first.c new file mode 100644 index 00000000..5b3f144d --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/solution/delete_first.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include +#include + +char *delete_first(char *s, char *pattern) +{ + char *found = strstr(s, pattern); + + /** + * Handle the case where the pattern does not appear in the + * string of characters. + */ + if (!found) + return strdup(s); + + /* number of characters before the first occurrence of the pattern */ + int nbefore = found - s; + /* number of characters to be removed */ + int nremoved = strlen(pattern); + /* Allocate exactly what we need. */ + char *result = malloc(strlen(s) + 1 - nremoved); + + /* Check if the allocation worked */ + if (result == NULL) { + perror("malloc"); + exit(1); + } + + /* Construct the result */ + strncpy(result, s, nbefore); + strcpy(result + nbefore, found + nremoved); + + return result; +} + +int main(void) +{ + /** + * Replace *s with s[], because *s allocated the string in a + * read-only memory area (.rodata), and the delete_first() function + * needs to modify the string s + */ + char s[] = "Ana are mere"; + char *pattern = "re"; + + printf("%s\n", delete_first(s, pattern)); + + return 0; +} diff --git a/chapters/memory-layout/memory-operations/drills/tasks/delete_first/support/.gitignore b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/support/.gitignore new file mode 100644 index 00000000..f1c8fd35 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/support/.gitignore @@ -0,0 +1 @@ +/delete_first diff --git a/chapters/memory-layout/memory-operations/drills/tasks/delete_first/support/Makefile b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/support/Makefile new file mode 100644 index 00000000..a689d3c2 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/support/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -Wall +LDFLAGS = + +TARGET_EXEC = delete_first + +SRCS := $(shell find $(SRC_DIRS) -name "*.c") +OBJS := $(SRCS:.c=.o) + +$(info OBJS is $(OBJS)) +$(info SRCS is $(SRCS)) + +$(TARGET_EXEC): $(OBJS) + $(CC) $(OBJS) -o $@ $(LDFLAGS) + +.o: %.c + $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@ + +.PHONY: clean + +clean: + $(RM) -r *.o $(TARGET_EXEC) diff --git a/chapters/memory-layout/memory-operations/drills/tasks/delete_first/support/delete_first.c b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/support/delete_first.c new file mode 100644 index 00000000..4a7a20ec --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/delete_first/support/delete_first.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include + +char *delete_first(char *s, char *pattern); + +int main(void) +{ + /** + * TODO: Is the declaration of the s variable correct considering that + * we're calling the delete_first function on it? Why? Modify if necessary. + */ + char *s = "Ana are mere"; + char *pattern = "re"; + + (void) s; + (void) pattern; + + /* Uncomment this line after implementing the delete_first function */ + /* printf("%s\n", delete_first(s, pattern)); */ + + return 0; +} diff --git a/chapters/memory-layout/memory-operations/drills/tasks/find_max/README.md b/chapters/memory-layout/memory-operations/drills/tasks/find_max/README.md new file mode 100644 index 00000000..cf8b28ad --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/find_max/README.md @@ -0,0 +1,15 @@ +# Find Maximum in Array + +You will solve the exercise starting from the `find-max.c` file located in the `drills/tasks/find-max/support` directory. + +Implement the following functions: + +```c +find_max(void *arr, int n, int element_size, int (*compare)(const void *, const void *)) +``` + +which calculates the maximum element from an array based on a given comparison function: + +```c +compare(const void *a, const void *b) +``` diff --git a/chapters/memory-layout/memory-operations/drills/tasks/find_max/solution/.gitignore b/chapters/memory-layout/memory-operations/drills/tasks/find_max/solution/.gitignore new file mode 100644 index 00000000..c5403377 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/find_max/solution/.gitignore @@ -0,0 +1 @@ +/find_max diff --git a/chapters/memory-layout/memory-operations/drills/tasks/find_max/solution/Makefile b/chapters/memory-layout/memory-operations/drills/tasks/find_max/solution/Makefile new file mode 120000 index 00000000..a39be4f2 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/find_max/solution/Makefile @@ -0,0 +1 @@ +../support/Makefile \ No newline at end of file diff --git a/chapters/memory-layout/memory-operations/drills/tasks/find_max/solution/find_max.c b/chapters/memory-layout/memory-operations/drills/tasks/find_max/solution/find_max.c new file mode 100644 index 00000000..e9354d06 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/find_max/solution/find_max.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include + +void *find_max(void *arr, int n, size_t element_size, + int (*compare)(const void *, const void *)) +{ + void *max_elem = arr; + + for (int i = 0; i < n; i++) { + void *cur_element = (char *)arr + i * element_size; + + if (compare(cur_element, max_elem) > 0) + max_elem = cur_element; + } + + return max_elem; +} + +int compare(const void *a, const void *b) +{ + return *(int *)a > *(int *)b ? 1 : 0; +} + +int main(void) +{ + int n; + + int *arr = malloc(n * sizeof(*arr)); + + if (arr == NULL) { + perror("malloc"); + exit(1); + } + + scanf("%d", &n); + + for (int i = 0 ; i < n; i++) + scanf("%d", &arr[i]); + + int *max_elem = (int *)find_max(arr, n, sizeof(*arr), compare); + + printf("The maximum element is: %d\n", *max_elem); + + free(arr); + return 0; +} diff --git a/chapters/memory-layout/memory-operations/drills/tasks/find_max/support/.gitignore b/chapters/memory-layout/memory-operations/drills/tasks/find_max/support/.gitignore new file mode 100644 index 00000000..c5403377 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/find_max/support/.gitignore @@ -0,0 +1 @@ +/find_max diff --git a/chapters/memory-layout/memory-operations/drills/tasks/find_max/support/Makefile b/chapters/memory-layout/memory-operations/drills/tasks/find_max/support/Makefile new file mode 100644 index 00000000..5587e1aa --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/find_max/support/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -Wall +LDFLAGS = + +TARGET_EXEC = find_max + +SRCS := $(shell find $(SRC_DIRS) -name "*.c") +OBJS := $(SRCS:.c=.o) + +$(info OBJS is $(OBJS)) +$(info SRCS is $(SRCS)) + +$(TARGET_EXEC): $(OBJS) + $(CC) $(OBJS) -o $@ $(LDFLAGS) + +.o: %.c + $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@ + +.PHONY: clean + +clean: + $(RM) -r *.o $(TARGET_EXEC) diff --git a/chapters/memory-layout/memory-operations/drills/tasks/find_max/support/find_max.c b/chapters/memory-layout/memory-operations/drills/tasks/find_max/support/find_max.c new file mode 100644 index 00000000..c48e2cd1 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/find_max/support/find_max.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include + +/** + * Generic function for calculating the maximum value from an array. + * n is the size of the array + * element_size is the size of an element in the array + * The arr vector will be traversed, during each iteration, we'll check + * if the compare function returns 1, in which case the current element will be + * the maximum one. + * To use pointer arithmetic correctly, we multiply the current iteration index + * by the size of an element. + * Thus, for accessing the current element we have: + * void *cur_element = (char *)arr + index * element_size; + */ +void *find_max(void *arr, int n, int element_size, + int (*compare)(const void *, const void *)) +{ + void *max_elem = arr; + + (void) n; + (void) element_size; + (void) compare; + + return max_elem; +} + +/** + * a and b are two pointers to void, but it is specified in the statement + * that the data at those addresses are of type int, so they need to be cast. + * The function returns 1 if the value at the address of a is greater than + * the value at the address of b, otherwise it returns 0. + */ +int compare(const void *a, const void *b); + +/** + * Reads a vector from the keyboard and asks to find the maximum element + * using the find_max function. + */ +int main(void) +{ + int n; + + scanf("%d", &n); + + int *arr = malloc(n * sizeof(*arr)); + + for (int i = 0 ; i < n; ++i) + scanf("%d", &arr[i]); + + free(arr); + return 0; +} diff --git a/chapters/memory-layout/memory-operations/drills/tasks/iterate/README.md b/chapters/memory-layout/memory-operations/drills/tasks/iterate/README.md new file mode 100644 index 00000000..05a084b5 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/iterate/README.md @@ -0,0 +1,30 @@ +# Iterating through an Integer Array + +You will solve the exercise starting from the `iterate.c` file located in the `drills/tasks/iterate/support` directory. + +Here is the given piece of C code: + +```c +#include + +int main() { + int v[] = {0xCAFEBABE, 0xDEADBEEF, 0x0B00B135, 0xBAADF00D, 0xDEADC0DE}; + + return 0; +} +``` + +Display the addresses of the elements in the `v` array along with their values. +Iterate through the addresses in `v` byte by byte, two bytes at a time, and four bytes at a time. + +> **TIP:** You can iterate through memory byte by byte starting from a specific address using a pointer of type `unsigned char*` (since the `char` type is represented by one byte). +> +>```c +>unsigned char *char_ptr = v; +>``` +> +> For displaying the address and the value, you can use: +> +>```c +>printf("%p -> 0x%x\n", char_ptr, *char_ptr); +>``` diff --git a/chapters/memory-layout/memory-operations/drills/tasks/iterate/solution/.gitignore b/chapters/memory-layout/memory-operations/drills/tasks/iterate/solution/.gitignore new file mode 100644 index 00000000..0a4109d5 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/iterate/solution/.gitignore @@ -0,0 +1 @@ +/iterate diff --git a/chapters/memory-layout/memory-operations/drills/tasks/iterate/solution/Makefile b/chapters/memory-layout/memory-operations/drills/tasks/iterate/solution/Makefile new file mode 120000 index 00000000..a39be4f2 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/iterate/solution/Makefile @@ -0,0 +1 @@ +../support/Makefile \ No newline at end of file diff --git a/chapters/memory-layout/memory-operations/drills/tasks/iterate/solution/iterate.c b/chapters/memory-layout/memory-operations/drills/tasks/iterate/solution/iterate.c new file mode 100644 index 00000000..3fd8f39f --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/iterate/solution/iterate.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include + +int main(void) +{ + unsigned int i; + int v[] = {0xCAFEBABE, 0xDEADBEEF, 0x0B00B135, 0xBAADF00D, 0xDEADC0DE}; + /** + * We will use pointers to elements of different sizes to + * facilitate the display of elements of 1, 2, and 4 bytes. + */ + unsigned char *char_ptr = (unsigned char *) &v; + unsigned short *short_ptr = (unsigned short *) &v; + unsigned int *int_ptr = (unsigned int *) &v; + + /* Iterate through each byte of the array v */ + for (i = 0 ; i < sizeof(v) / sizeof(*char_ptr); ++i) { + printf("%p -> 0x%x\n", char_ptr, *char_ptr); + ++char_ptr; + } + printf("-------------------------------\n"); + + /** + * Iterate through 2 bytes at a time, we have only half the steps because we are + * displaying 2 bytes at each step. + */ + for (i = 0 ; i < sizeof(v) / sizeof(*short_ptr); ++i) { + printf("%p -> 0x%x\n", short_ptr, *short_ptr); + ++short_ptr; + } + printf("-------------------------------\n"); + + /** + * Iterate through 4 bytes at a time, we have only a quarter of the steps because we are + * displaying 4 bytes at each step. + */ + for (i = 0 ; i < sizeof(v) / sizeof(*int_ptr); ++i) { + printf("%p -> 0x%x\n", int_ptr, *int_ptr); + ++int_ptr; + } + + return 0; +} diff --git a/chapters/memory-layout/memory-operations/drills/tasks/iterate/support/.gitignore b/chapters/memory-layout/memory-operations/drills/tasks/iterate/support/.gitignore new file mode 100644 index 00000000..0a4109d5 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/iterate/support/.gitignore @@ -0,0 +1 @@ +/iterate diff --git a/chapters/memory-layout/memory-operations/drills/tasks/iterate/support/Makefile b/chapters/memory-layout/memory-operations/drills/tasks/iterate/support/Makefile new file mode 100644 index 00000000..3b361e9d --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/iterate/support/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -Wall +LDFLAGS = + +TARGET_EXEC = iterate + +SRCS := $(shell find $(SRC_DIRS) -name "*.c") +OBJS := $(SRCS:.c=.o) + +$(info OBJS is $(OBJS)) +$(info SRCS is $(SRCS)) + +$(TARGET_EXEC): $(OBJS) + $(CC) $(OBJS) -o $@ $(LDFLAGS) + +.o: %.c + $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@ + +.PHONY: clean + +clean: + $(RM) -r *.o $(TARGET_EXEC) diff --git a/chapters/memory-layout/memory-operations/drills/tasks/iterate/support/iterate.c b/chapters/memory-layout/memory-operations/drills/tasks/iterate/support/iterate.c new file mode 100644 index 00000000..de954e07 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/iterate/support/iterate.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include + +/** + * Display the addresses of the elements in the "v" array + * along with their values. + * Traverse the addresses, one by one, byte by byte, then two by two bytes, + * and then four by four. + */ +int main(void) +{ + int v[] = {0xCAFEBABE, 0xDEADBEEF, 0x0B00B135, 0xBAADF00D, 0xDEADC0DE}; + + (void) v; + + return 0; +} diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pixels/README.md b/chapters/memory-layout/memory-operations/drills/tasks/pixels/README.md new file mode 100644 index 00000000..b5631d71 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pixels/README.md @@ -0,0 +1,40 @@ +# Pixels + +You will solve the exercise starting from the `pixels.c` file located in the `drills/tasks/pixels/support` directory. + +Consider the structure of a pixel and an image described in the `pixel.h` file: + +```c +typedef struct Pixel { + unsigned char R; + unsigned char G; + unsigned char B; +} Pixel; + +typedef struct Picture { + int height; + int width; + Pixel **pix_array; +} Picture; +``` + +Implement the following: + +- The `reversePic(Picture *pic)` function, which takes a Picture as a parameter and returns the reversed image. +By a reversed image, we mean the inversion of the rows of the `pix_array` matrix in the Picture structure. +- The `colorToGray(Picture *pic)` function, which takes a Picture as a parameter and returns the new image by converting each pixel to its grayscale value. +The grayscale value of a pixel is calculated using the following formula: + +```c +p.r = 0.3 * p.r; +p.g = 0.59 * p.g; +p.b = 0.11 * p.b; +``` + +> **IMPORTANT:** +> Accessing the elements of the pixel matrix will be done using pointer operations. +> **Hint:** For simplicity, you can use the following macro: +> +> ```c +> #define GET_PIXEL(a, i ,j) (*(*(a + i) + j)) +> ``` diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/.gitignore b/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/.gitignore new file mode 100644 index 00000000..897bde07 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/.gitignore @@ -0,0 +1 @@ +/pixels diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/Makefile b/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/Makefile new file mode 120000 index 00000000..a39be4f2 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/Makefile @@ -0,0 +1 @@ +../support/Makefile \ No newline at end of file diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/pixel.h b/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/pixel.h new file mode 120000 index 00000000..98f54793 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/pixel.h @@ -0,0 +1 @@ +../support/pixel.h \ No newline at end of file diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/pixels.c b/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/pixels.c new file mode 100644 index 00000000..694975df --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pixels/solution/pixels.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include +#include +#include +#include "pixel.h" + +#define GET_PIXEL(a, i, j) (*(*(a + i) + j)) + +void color_to_gray(struct picture *pic) +{ + for (int i = 0; i < pic->height; ++i) + for (int j = 0; j < pic->width; ++j) { + GET_PIXEL(pic->pix_array, i, j).R *= 0.3; + GET_PIXEL(pic->pix_array, i, j).G *= 0.59; + GET_PIXEL(pic->pix_array, i, j).B *= 0.11; + } +} + +void swap_rows(struct pixel *row1, struct pixel *row2, int width) +{ + /* Swap every from the two lines */ + for (int i = 0; i < width; ++i) { + struct pixel temp = row1[i]; + + row1[i] = row2[i]; + row2[i] = temp; + } +} + +void reverse_pic(struct picture *pic) +{ + for (int i = 0; i < pic->height / 2; ++i) + swap_rows(pic->pix_array[i], pic->pix_array[pic->height - 1 - i], + pic->height); +} + +int main(void) +{ + int height, width; + + scanf("%d%d", &height, &width); + struct pixel **pix_array = generate_pixel_array(height, width); + struct picture *pic = generate_picture(height, width, pix_array); + + print_picture(pic); + printf("\n"); + color_to_gray(pic); + print_picture(pic); + printf("\n"); + reverse_pic(pic); + print_picture(pic); + + free_picture(&pic); + free_pixel_array(&pix_array, height, width); + + return 0; +} diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/.gitignore b/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/.gitignore new file mode 100644 index 00000000..897bde07 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/.gitignore @@ -0,0 +1 @@ +/pixels diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/Makefile b/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/Makefile new file mode 100644 index 00000000..87cae77e --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -Wall +LDFLAGS = + +TARGET_EXEC = pixels + +SRCS := $(shell find $(SRC_DIRS) -name "*.c") +OBJS := $(SRCS:.c=.o) + +$(info OBJS is $(OBJS)) +$(info SRCS is $(SRCS)) + +$(TARGET_EXEC): $(OBJS) + $(CC) $(OBJS) -o $@ $(LDFLAGS) + +.o: %.c + $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@ + +.PHONY: clean + +clean: + $(RM) -r *.o $(TARGET_EXEC) diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/pixel.h b/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/pixel.h new file mode 100644 index 00000000..84440171 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/pixel.h @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#ifndef __PIXEL_H__ +#define __PIXEL_H__ + +struct pixel { + unsigned char R; + unsigned char G; + unsigned char B; +}; + +struct picture { + int height; + int width; + struct pixel **pix_array; +}; + +struct picture *generate_picture(int height, int width, struct pixel **pix_array) +{ + struct picture *pic = malloc(sizeof(*pic)); + + if (pic == NULL) { + perror("malloc"); + exit(1); + } + + pic->height = height; + pic->width = width; + + pic->pix_array = malloc(pic->height * sizeof(*pic->pix_array)); + if (pic->pix_array == NULL) { + perror("malloc"); + exit(1); + } + + for (int i = 0; i < pic->height; ++i) { + pic->pix_array[i] = malloc(pic->width * sizeof(**pic->pix_array)); + if (pic->pix_array[i] == NULL) { + perror("malloc"); + exit(1); + } + } + + for (int i = 0; i < pic->height; ++i) + for (int j = 0; j < pic->width; ++j) + pic->pix_array[i][j] = pix_array[i][j]; + + return pic; +} + +struct pixel generate_pixel(const unsigned char R, + const unsigned char G, const unsigned char B) +{ + struct pixel pixel; + + pixel.R = R; + pixel.G = G; + pixel.B = B; + + return pixel; +} + +void print_picture(struct picture *pic) +{ + for (int i = 0; i < pic->height; ++i) { + for (int j = 0; j < pic->width; ++j) { + printf("%u %u %u ", pic->pix_array[i][j].R, + pic->pix_array[i][j].G, pic->pix_array[i][j].B); + if (j != pic->width - 1) + printf(" | "); + } + printf("\n"); + } +} + +struct pixel **generate_pixel_array(int height, int width) +{ + struct pixel **pix_array = malloc(height * sizeof(*pix_array)); + + if (pix_array == NULL) { + perror("malloc"); + exit(1); + } + + for (int i = 0; i < height; ++i) { + pix_array[i] = malloc(width * sizeof(**pix_array)); + if (pix_array[i] == NULL) { + perror("malloc"); + exit(1); + } + } + + time_t t; + + srand((unsigned int) time(&t)); + + for (int i = 0; i < height; ++i) + for (int j = 0; j < width; ++j) + pix_array[i][j] = generate_pixel(rand() % 256, + rand() % 256, rand() % 256); + + return pix_array; +} + +void free_pixel_array(struct pixel ***pix_array, int height, int width) +{ + (void) width; + + for (int i = 0 ; i < height ; ++i) + free((*pix_array)[i]); + + free(*pix_array); +} + +void free_picture(struct picture **pic) +{ + free_pixel_array(&(*pic)->pix_array, (*pic)->height, (*pic)->width); + free(*pic); +} + +#endif // __PIXEL_H__ diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/pixels.c b/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/pixels.c new file mode 100644 index 00000000..6d72c659 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pixels/support/pixels.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include +#include +#include +#include "pixel.h" + +/** + * TODO a + * The function takes an image as a parameter and returns the reversed image. + * By reversed image we mean the reversal of the rows of the pix_array matrix + * from the picture structure, as follows: Line 1 becomes line n, line 2 becomes + * line n - 1, etc. + */ +void reverse_pic(struct picture *pic); + +/** + * TODO b + * The function takes an image as a parameter and returns the new image obtained + * by converting each pixel to its grayscale value. + * The grayscale value of a pixel is calculated according to the following formula: + * p.r = 0.3 * p.r; + * p.g = 0.59 * p.g; + * p.b = 0.11 * p.b; + */ +void color_to_gray(struct picture *pic); + +/** + * The structure of a pixel, that of an image, as well as their generation, + * are defined in pixel.h. The program receives from stdin the height + * and width of the image. It is preferable to enter small values + * that can be easily checked later. + * Use the print_picture function to print the components of the image. + * After completing a TODO, call the corresponding function in main + * followed by print_picture to see if the desired result is obtained. + */ +int main(void) +{ + int height, width; + + scanf("%d%d", &height, &width); + struct pixel **pix_array = generate_pixel_array(height, width); + struct picture *pic = generate_picture(height, width, pix_array); + + print_picture(pic); + + free_picture(&pic); + free_pixel_array(&pix_array, height, width); + + return 0; +} diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pointers/README.md b/chapters/memory-layout/memory-operations/drills/tasks/pointers/README.md new file mode 100644 index 00000000..d299ebee --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pointers/README.md @@ -0,0 +1,5 @@ +# Pointers + +You will solve the exercise starting from the `pointers.c` file located in the `drills/tasks/pointers/support` directory. + +Implement the functions [memcpy](http://www.cplusplus.com/reference/cstring/memcpy/), [strcpy](http://www.cplusplus.com/reference/cstring/strcpy/), and [strcmp](http://www.cplusplus.com/reference/cstring/strcmp/) using pointer operations. diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pointers/solution/.gitignore b/chapters/memory-layout/memory-operations/drills/tasks/pointers/solution/.gitignore new file mode 100644 index 00000000..0660c12d --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pointers/solution/.gitignore @@ -0,0 +1 @@ +/pointers diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pointers/solution/Makefile b/chapters/memory-layout/memory-operations/drills/tasks/pointers/solution/Makefile new file mode 120000 index 00000000..a39be4f2 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pointers/solution/Makefile @@ -0,0 +1 @@ +../support/Makefile \ No newline at end of file diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pointers/solution/pointers.c b/chapters/memory-layout/memory-operations/drills/tasks/pointers/solution/pointers.c new file mode 100644 index 00000000..d92c52f0 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pointers/solution/pointers.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include +#include + +#define SIGN(X) (((X) > 0) - ((X) < 0)) + +int my_strcmp(const char *s1, const char *s2) +{ + /** + * We iterate through the two character strings until we find a different character + * or determine that they are identical. + */ + for (; *s1 == *s2 ; ++s1, ++s2) + if (*s1 == '\0') + return 0; // identice + + /* Return the difference between the 2 different characters */ + return *(const unsigned char *) s1 - *(const unsigned char *) s2; +} + +void *my_memcpy(void *dest, const void *src, size_t n) +{ + size_t i; + unsigned char *d = (unsigned char *) dest; + const unsigned char *s = (const unsigned char *) src; + + /* Copy n bytes */ + for (i = 0 ; i < n ; ++i) + *d++ = *s++; + + return dest; +} + +char *my_strcpy(char *dest, const char *src) +{ + char *old_dest = dest; + + /* Copy until the string terminator ('\0' == 0). */ + while ((*dest++ = *src++) != 0) + ; + + return old_dest; +} + +int main(void) +{ + char s1[] = "Abracadabra"; + char s2[] = "Abracababra"; + char src[] = "Learn IOCLA, you must!"; + char *dest = malloc(sizeof(src)); + + /* testing */ + assert(SIGN(my_strcmp(s1, s2)) == SIGN(strcmp(s1, s2))); + assert(my_memcpy(dest, src, sizeof(src)) == memcpy(dest, src, sizeof(src))); + assert(my_strcpy(dest, src) == strcpy(dest, src)); + + free(dest); + + return 0; +} diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pointers/support/.gitignore b/chapters/memory-layout/memory-operations/drills/tasks/pointers/support/.gitignore new file mode 100644 index 00000000..0660c12d --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pointers/support/.gitignore @@ -0,0 +1 @@ +/pointers diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pointers/support/Makefile b/chapters/memory-layout/memory-operations/drills/tasks/pointers/support/Makefile new file mode 100644 index 00000000..14c11646 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pointers/support/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -Wall +LDFLAGS = + +TARGET_EXEC = pointers + +SRCS := $(shell find $(SRC_DIRS) -name "*.c") +OBJS := $(SRCS:.c=.o) + +$(info OBJS is $(OBJS)) +$(info SRCS is $(SRCS)) + +$(TARGET_EXEC): $(OBJS) + $(CC) $(OBJS) -o $@ $(LDFLAGS) + +.o: %.c + $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@ + +.PHONY: clean + +clean: + $(RM) -r *.o $(TARGET_EXEC) diff --git a/chapters/memory-layout/memory-operations/drills/tasks/pointers/support/pointers.c b/chapters/memory-layout/memory-operations/drills/tasks/pointers/support/pointers.c new file mode 100644 index 00000000..3dae7b20 --- /dev/null +++ b/chapters/memory-layout/memory-operations/drills/tasks/pointers/support/pointers.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include +#include + +#define SIGN(X) (((X) > 0) - ((X) < 0)) + +int my_strcmp(const char *s1, const char *s2); +void *my_memcpy(void *dest, const void *src, size_t n); +char *my_strcpy(char *dest, const char *src); + +int main(void) +{ + char s1[] = "Abracadabra"; + char s2[] = "Abracababra"; + char src[] = "Learn IOCLA, you must!"; + + char *dest_str = malloc(sizeof(src)); + char *dest_mem = malloc(sizeof(src)); + + if (!dest_str || !dest_mem) { + perror("malloc() failed"); + return 1; + } + + (void) s1; + (void) s2; + + /** + * Uncomment one assert at a time as you implement a function. + * If your function is implemented correctly, the assertion will succeed. + * Otherwise, the program will crash. + */ + /* assert(SIGN(my_strcmp(s1, s2)) == SIGN(strcmp(s1, s2))); */ + /* assert(my_strcpy(dest_str, src) && !strcmp(dest_str, src)); */ + /* assert(my_memcpy(dest_mem, src, sizeof(src)) && !memcmp(dest_mem, src, sizeof(src))); */ + + free(dest_str); + free(dest_mem); + return 0; +} diff --git a/chapters/memory-layout/memory-operations/media/arit.svg b/chapters/memory-layout/memory-operations/media/arit.svg new file mode 100644 index 00000000..bee8b721 --- /dev/null +++ b/chapters/memory-layout/memory-operations/media/arit.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/chapters/memory-layout/memory-operations/media/arrays.svg b/chapters/memory-layout/memory-operations/media/arrays.svg new file mode 100644 index 00000000..1d8d1b57 --- /dev/null +++ b/chapters/memory-layout/memory-operations/media/arrays.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/chapters/memory-layout/memory-operations/media/padding.svg b/chapters/memory-layout/memory-operations/media/padding.svg new file mode 100644 index 00000000..dcf12e39 --- /dev/null +++ b/chapters/memory-layout/memory-operations/media/padding.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/chapters/memory-layout/memory-operations/reading/README.md b/chapters/memory-layout/memory-operations/reading/README.md new file mode 100644 index 00000000..565ff6ba --- /dev/null +++ b/chapters/memory-layout/memory-operations/reading/README.md @@ -0,0 +1,367 @@ +# Pointers + +In the C programming language, memory interaction is achieved through pointers. +We remind you that a pointer is a variable that holds a memory address. +The general declaration form is as follows: `type *variable_name`, where `variable_name` can represent any valid data type in C. + +> **WARNING:** The **asterisk (`*`)** used in declaring a pointer denotes that it is a pointer and should not be confused with the dereference operator. +These are two entirely different concepts represented by the same symbol. +> Declaring a pointer does not mean allocating a memory area to store data. +A pointer is also a data type, whose value is a number representing a memory address. +> +> ```c +> int *p = 0xCAFEBABE; /* Declaring a pointer */ +> int x = *p; /* The value at the address stored in p */ +> ``` + +In C, a pointer can represent: + +- The address of data of a certain type +- The address of a memory area +- The address of a function +- The address of an area with unknown content (void pointer) + +> **TIP:** The size of a pointer depends on the architecture and operating system on which the program was compiled. +The size of a pointer is determined by `sizeof(void*)` and is not necessarily equal to the size of an `int`. + +## Pointer Operations and Pointer Arithmetic + +Arithmetic operations on pointers are slightly different from those on integer data types. +The only valid operations are **incrementing** or **decrementing** a pointer, **adding** or **subtracting** an integer from a pointer, and subtracting two pointers of the **same type**. +The behavior of these operations is influenced by the data type to which the pointers refer. + +When incrementing a pointer related to a data type `T`, the address is not increased by 1 but by the value `sizeof(T)`, which ensures addressing the next object of the same type. +Similarly, adding an integer `n` to a pointer `p` (thus the operation `p + n`) actually represents `p + n * sizeof(*p)`. +For example: + +```c +char *char_ptr = 1000; +short *short_ptr = 2000; +int *int_ptr = 3000; + +++char_ptr; /* char_ptr will point to address 1001 */ +++short_ptr; /* short_ptr points to address 2002 */ +++int_ptr; /* int_ptr points to address 3004 */ +``` + +![A diagram which visualizes arithmetic operations on pointers](../media/arit.svg) + +Subtracting two pointers is possible only if both have the same type. +The result of the subtraction is obtained by calculating the difference between the memory addresses they point to. +For example, calculating the length of a string: + +```c +char *s = "Learn IOCLA, you must!"; +char *p = s; +for (; *p; ++p); /* Iterating character by character until '\0' */ + +printf("%ld", p - s); /* It will display 22. */ +``` + +### Interpreting Data in Memory + +On most modern computers, the smallest unit of data that can be addressed is the `byte` (8 bits), meaning that we can view data in memory as a sequence of bytes, each with its own address. +As mentioned in the [previous lab](../../../intro-computer-architecture/binary-hex/reading), when we want to store information represented by multiple bytes, we need to consider the order imposed by the system architecture, called [endianness](https://en.wikipedia.org/wiki/Endianness). +Below is the mechanism for extracting data from memory on a **little-endian** architecture: + +```c +int n = 0xCAFEBABE; +unsigned char first_byte = *((unsigned char*) &n); /* Extracting the first byte of n */ +unsigned char second_byte = *((unsigned char*) &n + 1); /* Extracting the second byte of n */ +printf("0x%x, 0x%x\n", first_byte, second_byte); /* It will display 0xBE, 0xBA */ +``` + +> **NOTE:** For casted pointers, arithmetic operations are performed on the type to which they have been cast. +> +> **WARNING:** Do not confuse `*p++` with `(*p)++`. +In the first case, it increments the address pointed by `p`, while in the second case, it increments the value at that address. +> Arithmetic on pointers of type `void` is not possible due to the lack of a concrete data type they point to. + +### Pointers to Arrays + +There is a very close relationship between pointers and arrays. +In C, the name of an array is a *constant pointer* (its address is allocated by the compiler and cannot be modified during execution) to the first element of the array: `v = &v[0]`. +For example: + +```c +int v[10], *p; +p = v; +++p; /* Correct */ +++v; /* ERROR */ +``` + +Arrays are stored in a continuous block of memory, so pointer arithmetic works the same way for arrays as well. +Here are some equivalences: + +```c +v[0] <==> *v +v[1] <==> *(v + 1) +v[n] <==> *(v + n) +&v[0] <==> v +&v[1] <==> v + 1 +&v[n] <==> v + n +``` + +Additionally, an array also contains information about its length and the total size occupied in memory, so `sizeof(v)` will return the space occupied in memory (number of bytes), and `sizeof(v) / sizeof(*v)` will return the number of elements in `v`. + +Using pointers, we can dynamically allocate memory. +In this sense, dynamic allocation of a two-dimensional array (a matrix) can be done as follows: + +The traditional method, where we allocate an array of pointers to pointers: + +```c +int **array1 = malloc(nrows * sizeof(*array1)); +for (i = 0; i < nrows; ++i) + array1[i] = malloc(ncolumns * sizeof(**array1)); +``` + +If we want to keep the array in a continuous block of memory: + +```c +int **array2 = malloc(nrows * sizeof(*array2)); +array2[0] = malloc(nrows * ncolumns * sizeof(**array2)); +for (i = 1; i < nrows; ++i) + array2[i] = array2[0] + i * ncolumns; +``` + +Below is the difference between the two approaches: + +![A diagram which showcases the fact that the second approach keeps all the elements in a continuous block of memory, while the first fragments the lines in different places in memory](../media/arrays.svg) + +In both cases, the elements of the matrix can be accessed using the indexing operator `[]`: `arrayX[i][j]`. +Also, just like with vectors, we can replace indexing with pointer operations. +Thus, `arr[i][j] = *(arr + i)[j] = *(*(arr + i) + j)`. + +> **WARNING:** Whenever you allocate memory using a pointer, use `p = malloc(n * sizeof(*p))` instead of `p = malloc(n * sizeof(int))`. +Using `sizeof(*p)` makes the code more robust and self-documenting, so anyone reading the code will see that the correct number of bytes is being allocated without needing to check the data type that `p` is pointing to. + +## Structures and Pointers to Structures + +Structures are data types in which we can group multiple variables, possibly of different types (unlike arrays, which contain only data of the same type). +A structure can be defined as follows: + +```c +struct struct_name { + field_declarations +}; +``` + +For simplifying declarations, we can associate a structure with a data type name: `typedef struct {field_declarations} struct_name;` + +```c +typedef struct student { + char *name; + int year; + float grade; +} Student; + +int main() { + Student s; + s.name = (char *) malloc(20 * sizeof(*s.name)); + s.year = 3; + return 0; +} +``` + +Accessing members of a structure is done using the `.` operator. + +In the case of pointers to structures, accessing members is done by dereferencing the pointers: + +```c +Student *s = (Student *) malloc(sizeof(*s)); +(*s).year = 3; +/* In practice, to ease writing, the "->" operator is used */ +s->year = 4; +``` + +The size of a structure is not always equal to the sum of the sizes of its fields. +This happens because of padding added by the compiler to ensure proper memory alignment. +Padding is added after a structure member followed by another member with a larger size, or at the end of the structure. + +```c +struct A { + /* sizeof(int) = 4 */ + int x; + /* Padding with 4 bytes */ + + /* sizeof(double) = 8 */ + double z; + + /* sizeof(short) = 2 */ + short y; + /* Padding with 6 bytes */ +}; + +printf("Size of struct: %zu", sizeof(struct A)) /* Will print 24 */ +``` + +![A diagram visualizing the padding of each structure field, 4 bytes for x, 6 for y, and 0 for z](../media/padding.svg) + +The red portion represents the padding added by the compiler, and the green parts represent the structure's members. + +However, we can prevent the compiler from adding padding by using `__attribute__((packed))` when declaring the structure (More details about this in the Computer Communication Protocols course). +Thus, for the previous example, the result would be 14. + +> **NOTE:** If you declare pointers to structures, don't forget to allocate memory for them before accessing the structure fields. +Also, remember to allocate and initialize structure fields that are pointer types before using them. +Also, pay attention to how you access structure fields. + +## Void Pointers + +Memory can be seen as an array of bytes, accessible through pointers. +By the type of the pointer, the addressed memory area gains a certain interpretation, as discussed above. +There are cases where we want to address a zone of this *'array'* in a generic way, thus requiring **void pointers**. + +A pointer to `void` is a pointer that does not have an associated type. +Void pointers have a **high flexibility** because they can point to any type of data, but they also have a **limitation** in that they cannot be dereferenced, and to be used in pointer operations, they need to be converted to a known data type. + +They are most commonly used in the implementation of **generic functions**. +For example, the functions `malloc()` and `calloc()` return a pointer to void, allowing these functions to be used for memory allocation for any data type. + +An example of using void pointers is as follows: + +```c +#include + +void increment(void *data, int element_size) { + /* Check if the data entered is a char */ + if (element_size == sizeof(char)) { + /* As mentioned, to be dereferenced, + * a void pointer needs to be cast + */ + char *char_ptr = data; + ++(*char_ptr); + } + + if (element_size == sizeof(int)) { + int *int_ptr = data; + ++(*int_ptr); + } +} + +int main() { + char c = 'a'; + int x = 10; + + increment(&c, sizeof(c)); + increment(&x, sizeof(x)); + + printf("%c, %d\n", c, x); /* Will print: b, 11 */ + return 0; +} +``` + +> **NOTE:** In `C`, it is not necessary to explicitly cast the assignment of a pointer of type T with a `void*` pointer. +> +> **Example (Good practice):** +> +> ```c +> int *array = malloc(sizeof(int) * number_of_elements); +> ``` +> +> **NOT like this:** +> +> ```c +> int *array = (int*) malloc(sizeof(int) * number_of_elements); +> ``` +> + +## Pointers in Functions and Function Pointers + +Within functions, pointers can be used for: + +- Passing results through arguments +- Passing an address through the function's return +- Passing other functions and subsequently using them + +A function that needs to modify multiple values passed through arguments or that needs to transmit multiple calculated results within the function should use pointer arguments. + +```c +#include + +void swap(int *a, int *b) { + int c = *a; + *a = *b; + *b = c; +} + +int main() { + int a = 3, b = 5; + swap(&a, &b); + + printf("a = %d, b = %d\n", a, b); /* Will print a = 5, b = 3 */ + + return 0; +} +``` + +A function can return a pointer, but this pointer cannot contain the address of a local variable. +Most of the time, the result is one of the arguments, possibly modified within the function. +For example: + +```c +char* toUpper(char *s) { + /* Takes a string and returns the string in uppercase */ + for (int i = 0 ; s[i] ; ++i) { + if (s[i] >= 'a' && s[i] <= 'z') { + s[i] -= 32; + } + } + + return s; +} +``` + +If a function returns the address of a local variable, it must be static. +The lifetime of a local variable ends when the execution of the function in which it was defined ends, and therefore the address of such a variable should not be passed outside the function. + +The name of a function represents the memory address at which the function begins. +A function pointer is a variable that stores the address of a function that can be called later through that pointer. +Usually, function pointers are used to pass a function as a parameter to another function. + +The declaration of a function pointer is done as follows: `type (*pf) (formal_parameter_list)` + +Why is it necessary to use extra parentheses? +Without them, we would be talking about a function that returns a pointer. +Below are two examples of using function pointers: + +```c +int add(int a, int b) { + return a + b; +} + +int subtract(int a, int b) { + return a - b; +} + +int operation(int x, int y, int (*func) (int, int)) { + return func(x, y); +} + +int main() { + int (*minus)(int, int) = subtract; + printf("%d", operation(10, 5, minus)); /* Will print 5 */ + + return 0; +} +``` + +The [qsort()](http://www.cplusplus.com/reference/cstdlib/qsort/) function from `stdlib.h` uses a function pointer as a comparator. + +```c +int compare(const void *a, const void *b) { + return *(int *) a - *(int *)b; +} + +int main() { + int v[] = {100, 5, 325, 1, 30}; + int size = sizeof(v) / sizeof(*v); + + qsort(v, size, sizeof(*v), compare); + for (int i = 0 ; i < size ; ++i) { + printf("%d ", v[i]); + } + + return 0; +} +``` diff --git a/config.yaml b/config.yaml index d9d85131..a667a972 100644 --- a/config.yaml +++ b/config.yaml @@ -62,6 +62,39 @@ docusaurus: subsections: - Length and Equality with Bitwise Operations/: len-xor/ - Mirror/: mirror/ + - Lab 2 - Memory Operations. Introduction to GDB: + - Memory Operations: + path: chapters/memory-layout/memory-operations/ + extra: + - media/ + subsections: + - Reading/: reading/ + - Drills: + path: drills/ + subsections: + - Tasks: + path: tasks/ + subsections: + - Iterating through an Integer Array/: iterate/ + - Deleting the First Occurrence of a Pattern from a String/: delete_first/ + - Pixels/: pixels/ + - Find Maximum in Array/: find_max/ + - "Bonus: Pointers/": pointers/ + - Introduction to GDB: + path: chapters/memory-layout/introduction-to-GDB/ + subsections: + - Reading/: reading/ + - Guides: + path: guides/ + subsections: + - "GDB Tutorial: Debugging a Segfault/": segfault/ + - Drills: + path: drills/ + subsections: + - Tasks: + path: tasks/ + subsections: + - Data Inspection/: inspect/ # static_assets: # - template-chapter-template-topic: /build/make_assets/chapters/template-chapter/template-topic/slides/_site config_meta: