Dynamic memory in C — exercises to raise grades, filter the passes and keep a sorted array that grows (and the double free that knocked out my report)
The week I closed my first “grade report” in C I felt unstoppable: malloc, realloc with a temporary, free… all in order. I wrote a function close_report that computed the average and, to be tidy, freed the array. And in main, to be tidy as well, I freed it again. Result: free(): double free detected in tcache 2 and the program aborted right after printing the average. Two “just in case” free calls are one too many. That bug is the third “What’s wrong here?” snippet, and the idea it leaves (every block has one owner) runs through all the exercises.
Here I no longer give you the code. You’ve got three exercises of increasing difficulty with a class’s grades, three snippets with bugs for you to predict what happens and, at the end, the commented solutions and the cheat sheet. If you get stuck on something basic, go back to the dynamic memory in C theory or to the practice.
Table of Contents
How to use this article
- Read a level’s statement and write your solution without looking at mine. Compile with
gcc -Wall -std=c11 -g -fsanitize=address. - Compare your output with the expected output, character by character.
- If it doesn’t work, open the hint. If it still doesn’t, look at the solution, close it and try again from scratch.
- Do “What’s wrong here?” before reading the explanations: predicting the bug is the best training.
Level 1 (basic): raise the grades without touching the originals
The teacher wants to see what the grades would look like if everyone got half a point more, but without changing the real grades. Write a function
double *raise_grades(const double *v, int n, double extra)
that returns a new array (reserved with malloc) with each grade raised by extra points, never above 10. In main, with the grades {4.5, 6.0, 9.8, 3.0, 7.5} and extra = 0.5, show the originals and the raised ones, and free whatever needs freeing.
Expected output:
Original: 4.5 6.0 9.8 3.0 7.5 Raised : 5.0 6.5 10.0 3.5 8.0
The function reserves, but the one using the result is main: where does the free go? And for the cap, an if after adding is enough.
Level 2 (intermediate): filter the passes with just enough memory
Write
double *filter_passed(const double *v, int n, int *k)
that returns a new array with only the passes (grade ≥ 5) and leaves in *k how many there are. It must reserve exactly the memory needed (not one slot more) and return NULL if nobody passed. Test it with two groups: {6.5, 4.0, 9.5, 5.0, 7.0, 3.0, 8.5} and {2.0, 4.5}.
Expected output:
Group A: 5 of 7 passed -> 6.5 9.5 5.0 7.0 8.5 Group B: 0 of 2 passed -> nobody
To reserve just enough you need to know how many passes there are before the malloc. Go through the array twice.
Level 3 (challenge): a sorted array that grows and cleans itself up
The grades arrive unsorted: {6.5, 4.0, 9.5, 5.0, 7.0, 3.0, 8.5}. Write:
int insert_sorted(double **v, int *n, int *cap, double x): insertsxin its place so the array is always sorted from lowest to highest. If it doesn’t fit, it doubles the capacity withrealloc. Returns 1 if OK and 0 if there’s no memory.void remove_fails(double **v, int *n, int *cap): removes the grades below 5 and shrinks the array to its exact size.
Start with capacity 1 and show the array, n and cap after each insertion and at the end.
Expected output:
+6.5 -> [6.5] n=1 cap=1 +4.0 -> [4.0 6.5] n=2 cap=2 +9.5 -> [4.0 6.5 9.5] n=3 cap=4 +5.0 -> [4.0 5.0 6.5 9.5] n=4 cap=4 +7.0 -> [4.0 5.0 6.5 7.0 9.5] n=5 cap=8 +3.0 -> [3.0 4.0 5.0 6.5 7.0 9.5] n=6 cap=8 +8.5 -> [3.0 4.0 5.0 6.5 7.0 8.5 9.5] n=7 cap=8 No fails -> [5.0 6.5 7.0 8.5 9.5] n=5 cap=5
To insert in order, start from the end and shift every grade bigger than x one slot to the right; the gap left behind is its place. To remove the fails, use a read index and a write index, like in the practice’s report.
What’s wrong here?
Three snippets with one bug each. Read the code, predict what happens and then open the explanation.
Snippet 1: the absences
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 8; /* 8 students */
int *absences = malloc(n); /* absences of each student */
if (absences == NULL) return 1;
for (int i = 0; i < n; i++)
absences[i] = i % 3;
printf("Absences of the last one: %d\n", absences[n - 1]);
free(absences);
return 0;
}
What do you think happens? Without tools, it’ll most likely print a number and look fine. Compiled with gcc -Wall -std=c11 -g -fsanitize=address, this is the output (the AddressSanitizer report, trimmed to the important lines):
=================================================================
==723==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000018
WRITE of size 4 at 0x502000000018 thread T0
#0 in main snippet.c:9
0x502000000018 is located 0 bytes after 8-byte region [0x502000000010,0x502000000018)
allocated by thread T0 here:
#1 in main snippet.c:6
SUMMARY: AddressSanitizer: heap-buffer-overflow snippet.c:9 in main
malloc(n) reserves n bytes: 8 bytes. An int takes 4, so only 2 absences fit. In round i = 2 the program writes 4 bytes right after the block, and AddressSanitizer catches it: WRITE of size 4, 0 bytes after 8-byte region, line 9 (absences[i] = i % 3;), on a block reserved on line 6.
A program with this bug “working” doesn’t mean it’s right. You’re trampling memory that may belong to other data, and the failure will show up when you least expect it.
The fix is to multiply by the size of each slot:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 8; /* 8 students */
int *absences = malloc(n * sizeof *absences); /* 8 int slots = 32 bytes */
if (absences == NULL) return 1;
for (int i = 0; i < n; i++)
absences[i] = i % 3;
printf("Absences of the last one: %d\n", absences[n - 1]);
free(absences);
return 0;
}
Output:
Absences of the last one: 1
The last absence count belongs to student 7: 7 % 3 = 1.
With sizeof *absences you don’t have to remember the type. If tomorrow the absences become short, the line is still correct.
Snippet 2: the miscounted dropouts
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 3;
double *grades = malloc(n * sizeof(double));
if (grades == NULL) return 1;
grades[0] = 7.5; grades[1] = 4.0; grades[2] = 9.0;
int dropouts = 4; /* 4 drop out... but there were only 3 */
int new_n = n - dropouts;
printf("Adjusting to %d grades...\n", new_n);
grades = realloc(grades, new_n * sizeof(double));
printf("First grade: %.1f\n", grades[0]);
free(grades);
return 0;
}
What do you think happens? Real output:
Adjusting to -1 grades... Segmentation fault (core dumped)
There are two chained bugs. The first is an arithmetic one: there were 3 grades and 4 dropouts are subtracted, so new_n is -1. When multiplying -1 * sizeof(double), the -1 is converted to size_t (an unsigned integer) and turns into a gigantic number: realloc is being asked for almost 18 quintillion bytes. Naturally, it fails and returns NULL.
The second bug is the one that turns an arithmetic slip into a disaster: grades = realloc(grades, ...) without a temporary. Now grades is NULL, the 3-grade block stays reserved with nobody able to reach it, and grades[0] reads address 0: Segmentation fault.
Passing malloc or realloc a size you computed without checking it. A negative number becomes a huge size without any warning.
The fix checks the numbers and uses a temporary:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 3;
double *grades = malloc(n * sizeof(double));
if (grades == NULL) return 1;
grades[0] = 7.5; grades[1] = 4.0; grades[2] = 9.0;
int dropouts = 4;
int new_n = n - dropouts;
if (new_n < 1) { /* we check the numbers before requesting memory */
printf("Impossible size (%d): keeping %d grades\n", new_n, n);
} else {
double *tmp = realloc(grades, new_n * sizeof(double));
if (tmp != NULL) { grades = tmp; n = new_n; } /* if it fails, grades is intact */
}
printf("First grade: %.1f\n", grades[0]);
free(grades);
return 0;
}
Output:
Impossible size (-1): keeping 3 grades First grade: 7.5
Now the program detects the impossible size, keeps the 3 grades and carries on working.
In a real case, the arithmetic slip would need fixing too (why do 4 drop out if there are 3?). The temporary doesn’t fix the numbers, but it stops a failure from turning into data loss.
Snippet 3: the report that was closed twice
#include <stdio.h>
#include <stdlib.h>
void close_report(double *grades, int n) {
double sum = 0;
for (int i = 0; i < n; i++) sum += grades[i];
printf("Report closed: average %.2f\n", sum / n);
free(grades); /* the function frees it... */
}
int main(void) {
double *grades = malloc(4 * sizeof(double));
if (grades == NULL) return 1;
grades[0] = 7.5; grades[1] = 4.0; grades[2] = 9.0; grades[3] = 6.5;
close_report(grades, 4);
free(grades); /* ...and main frees it again */
printf("End of program\n");
return 0;
}
What do you think happens? Real output:
Report closed: average 6.75 free(): double free detected in tcache 2 Aborted (core dumped)
close_report receives a copy of the pointer, not a copy of the array: both grades (the one in main and the parameter) point to the same block. The function frees it and main frees it again. The C library notices the block was already free (double free detected) and aborts the program: “End of program” never gets printed.
The drawing shows the problem: two pointers, one single block and two free calls.
Freeing “just in case” in two places. Decide who owns the block and free it only there.
The fix: close_report only reads (we mark it with const) and the only free is in main, which is the one that reserved it.
#include <stdio.h>
#include <stdlib.h>
void close_report(const double *grades, int n) { /* const: it only reads, it is not the owner */
double sum = 0;
for (int i = 0; i < n; i++) sum += grades[i];
printf("Report closed: average %.2f\n", sum / n);
}
int main(void) {
double *grades = malloc(4 * sizeof(double));
if (grades == NULL) return 1;
grades[0] = 7.5; grades[1] = 4.0; grades[2] = 9.0; grades[3] = 6.5;
close_report(grades, 4);
free(grades); /* one single owner, one single free */
grades = NULL;
printf("End of program\n");
return 0;
}
Output:
Report closed: average 6.75 End of program
Average check: (7.5 + 4.0 + 9.0 + 6.5) / 4 = 27 / 4 = 6.75.
If a function frees a block it’s given, say so in its name or in a comment (destroy_report, “frees v”). And the caller should set its pointer to NULL right afterwards.
Solutions
Level 1 solution
#include <stdio.h>
#include <stdlib.h>
/* Returns a NEW array with each grade raised by 'extra' points (never above 10).
The original is not touched: that is why it is const. The caller frees the result. */
double *raise_grades(const double *v, int n, double extra) {
double *r = malloc(n * sizeof *r);
if (r == NULL) return NULL;
for (int i = 0; i < n; i++) {
r[i] = v[i] + extra;
if (r[i] > 10) r[i] = 10; /* cap: nobody gets a 10.3 */
}
return r;
}
void show(const char *title, const double *v, int n) {
printf("%s:", title);
for (int i = 0; i < n; i++) printf(" %.1f", v[i]);
printf("\n");
}
int main(void) {
double grades[] = {4.5, 6.0, 9.8, 3.0, 7.5};
int n = sizeof grades / sizeof grades[0];
double *raised = raise_grades(grades, n, 0.5);
if (raised == NULL) return 1;
show("Original", grades, n);
show("Raised ", raised, n);
free(raised); /* raise_grades reserved it, main frees it */
return 0;
}
Output:
Original: 4.5 6.0 9.8 3.0 7.5 Raised : 5.0 6.5 10.0 3.5 8.0
Why it’s done this way:
- **
const double *v**: the function promises not to modify the original grades. If you wrotev[i] += extraby mistake, the compiler would give an error. - **A new array with
malloc**: returning a local array would repeat the Segmentation fault from the theory. The result has to live on the heap. - The cap: 9.8 + 0.5 = 10.3, which is cut down to 10.0. The rest: 4.5 + 0.5 = 5.0, 6.0 + 0.5 = 6.5, 3.0 + 0.5 = 3.5 and 7.5 + 0.5 = 8.0.
- **
free(raised)inmain** and neverfree(grades):gradesis a normal stack array, and freeing something that doesn’t come frommallocis a serious bug. - **
"Raised "with spaces** is just so the two lines line up.
Level 2 solution
#include <stdio.h>
#include <stdlib.h>
/* Returns a new array with ONLY the passing grades and leaves its size in *k.
Two passes: count and then copy, to request exactly the memory needed. */
double *filter_passed(const double *v, int n, int *k) {
*k = 0;
for (int i = 0; i < n; i++) /* pass 1: how many passing grades are there? */
if (v[i] >= 5) (*k)++;
if (*k == 0) return NULL; /* malloc(0) is useless: NULL = empty */
double *r = malloc(*k * sizeof *r);
if (r == NULL) { *k = 0; return NULL; }
int j = 0; /* j = next free slot of r */
for (int i = 0; i < n; i++) /* pass 2: copy */
if (v[i] >= 5) r[j++] = v[i];
return r;
}
void report(const char *group, const double *v, int n) {
int k;
double *passed = filter_passed(v, n, &k);
printf("%s: %d of %d passed ->", group, k, n);
if (passed == NULL) printf(" nobody");
for (int i = 0; i < k; i++) printf(" %.1f", passed[i]);
printf("\n");
free(passed); /* free(NULL) is safe: no if needed */
}
int main(void) {
double group_a[] = {6.5, 4.0, 9.5, 5.0, 7.0, 3.0, 8.5};
double group_b[] = {2.0, 4.5};
report("Group A", group_a, 7);
report("Group B", group_b, 2);
return 0;
}
Output:
Group A: 5 of 7 passed -> 6.5 9.5 5.0 7.0 8.5 Group B: 0 of 2 passed -> nobody
Zoom into the function, which is where the decisions are:
/* Returns a new array with ONLY the passing grades and leaves its size in *k.
Two passes: count and then copy, to request exactly the memory needed. */
double *filter_passed(const double *v, int n, int *k) {
*k = 0;
for (int i = 0; i < n; i++) /* pass 1: how many passing grades are there? */
if (v[i] >= 5) (*k)++;
if (*k == 0) return NULL; /* malloc(0) is useless: NULL = empty */
double *r = malloc(*k * sizeof *r);
if (r == NULL) { *k = 0; return NULL; }
int j = 0; /* j = next free slot of r */
for (int i = 0; i < n; i++) /* pass 2: copy */
if (v[i] >= 5) r[j++] = v[i];
return r;
}
Trace of group A in the second pass (*k is already 5, so 5 × 8 = 40 bytes are reserved):
| i | v[i] | ≥ 5? | Copied into | j after |
|---|---|---|---|---|
| 0 | 6.5 | Yes | r[0] | 1 |
| 1 | 4.0 | No | — | 1 |
| 2 | 9.5 | Yes | r[1] | 2 |
| 3 | 5.0 | Yes | r[2] | 3 |
| 4 | 7.0 | Yes | r[3] | 4 |
| 5 | 3.0 | No | — | 4 |
| 6 | 8.5 | Yes | r[4] | 5 |
Why it’s done this way:
- **Two passes instead of a
reallocfor each pass**: the first one only counts, so themallocis exact and there’s no need to grow or shrink. - **
*k == 0→NULL**:malloc(0)may returnNULLor a pointer you can’t use, depending on the system. ReturningNULLon purpose makes it clear the array is empty. - **If
mallocfails,*k = 0**: that way the caller never walkskslots of an array that doesn’t exist. - **
free(passed)with noif**: in group B,passedisNULLandfree(NULL)does nothing. The line works for both cases.
Level 3 solution
#include <stdio.h>
#include <stdlib.h>
void show(const double *v, int n, int cap) {
printf("[");
for (int i = 0; i < n; i++) printf(i ? " %.1f" : "%.1f", v[i]);
printf("] n=%d cap=%d\n", n, cap);
}
/* Inserts x keeping the array SORTED from lowest to highest. Doubles the capacity if needed. */
int insert_sorted(double **v, int *n, int *cap, double x) {
if (*n == *cap) {
double *tmp = realloc(*v, 2 * *cap * sizeof **v);
if (tmp == NULL) return 0;
*v = tmp;
*cap *= 2;
}
int i = *n - 1;
while (i >= 0 && (*v)[i] > x) { /* from the end, we shift the bigger ones to the right */
(*v)[i + 1] = (*v)[i];
i--;
}
(*v)[i + 1] = x; /* the gap left behind is where x goes */
(*n)++;
return 1;
}
/* Removes the failing grades by compacting the array and shrinks it to its exact size */
void remove_fails(double **v, int *n, int *cap) {
int j = 0;
for (int i = 0; i < *n; i++)
if ((*v)[i] >= 5) (*v)[j++] = (*v)[i]; /* the passing grades shift to the left */
*n = j;
if (j > 0 && j < *cap) {
double *tmp = realloc(*v, j * sizeof **v);
if (tmp != NULL) { *v = tmp; *cap = j; } /* if it fails, the big array is still valid */
}
}
int main(void) {
const double arriving[] = {6.5, 4.0, 9.5, 5.0, 7.0, 3.0, 8.5};
int n = 0, cap = 1;
double *grades = malloc(cap * sizeof *grades);
if (grades == NULL) return 1;
for (int i = 0; i < 7; i++) {
if (!insert_sorted(&grades, &n, &cap, arriving[i])) break;
printf("+%.1f -> ", arriving[i]);
show(grades, n, cap);
}
remove_fails(&grades, &n, &cap);
printf("No fails -> ");
show(grades, n, cap);
free(grades);
return 0;
}
Output:
+6.5 -> [6.5] n=1 cap=1 +4.0 -> [4.0 6.5] n=2 cap=2 +9.5 -> [4.0 6.5 9.5] n=3 cap=4 +5.0 -> [4.0 5.0 6.5 9.5] n=4 cap=4 +7.0 -> [4.0 5.0 6.5 7.0 9.5] n=5 cap=8 +3.0 -> [3.0 4.0 5.0 6.5 7.0 9.5] n=6 cap=8 +8.5 -> [3.0 4.0 5.0 6.5 7.0 8.5 9.5] n=7 cap=8 No fails -> [5.0 6.5 7.0 8.5 9.5] n=5 cap=5
The trickiest function is the insertion:
/* Inserts x keeping the array SORTED from lowest to highest. Doubles the capacity if needed. */
int insert_sorted(double **v, int *n, int *cap, double x) {
if (*n == *cap) {
double *tmp = realloc(*v, 2 * *cap * sizeof **v);
if (tmp == NULL) return 0;
*v = tmp;
*cap *= 2;
}
int i = *n - 1;
while (i >= 0 && (*v)[i] > x) { /* from the end, we shift the bigger ones to the right */
(*v)[i + 1] = (*v)[i];
i--;
}
(*v)[i + 1] = x; /* the gap left behind is where x goes */
(*n)++;
return 1;
}
Let’s see what happens when inserting 5.0, when the array is [4.0 6.5 9.5] with n = 3 and cap = 4 (it fits without growing):
| Step | i | (*v)[i] | > 5.0? | What it does | Array |
|---|---|---|---|---|---|
| Start | 2 | 9.5 | Yes | v[3] = v[2] | 4.0 6.5 9.5 9.5 |
| 2 | 1 | 6.5 | Yes | v[2] = v[1] | 4.0 6.5 6.5 9.5 |
| 3 | 0 | 4.0 | No | leaves the loop | 4.0 6.5 6.5 9.5 |
| End | 0 | — | — | v[i + 1] = 5.0 | 4.0 5.0 6.5 9.5 |
The drawing shows the “shift”: each grade bigger than 5.0 is copied one slot to the right (which is why, for a moment, a grade appears twice), and the gap left behind is exactly where 5.0 goes.
And the final clean-up:
/* Removes the failing grades by compacting the array and shrinks it to its exact size */
void remove_fails(double **v, int *n, int *cap) {
int j = 0;
for (int i = 0; i < *n; i++)
if ((*v)[i] >= 5) (*v)[j++] = (*v)[i]; /* the passing grades shift to the left */
*n = j;
if (j > 0 && j < *cap) {
double *tmp = realloc(*v, j * sizeof **v);
if (tmp != NULL) { *v = tmp; *cap = j; } /* if it fails, the big array is still valid */
}
}
Why it’s done this way:
- Grow before shifting: if the array is full,
(*v)[i + 1]withi = n - 1would write one slot outside the block. That’s why therealloccomes first. - **
i >= 0before(*v)[i] > x**:&&is evaluated left to right and stops as soon as something is false. Whenxis the smallest grade (3.0),ireaches -1 and the second condition isn’t even looked at: without that order, you’d readv[-1]. - The capacities: 1 → 2 (when 4.0 arrives) → 4 (9.5) → 8 (7.0). Three
realloccalls for seven grades. - **
remove_fails** uses read (i) and write (j) indexes like the practice’s report: 3.0 and 4.0 aren’t copied and 5 grades remain. Then it shrinks from 8 to 5 with a temporary. - **
j > 0** avoidsrealloc(*v, 0)if everyone failed: in that case the array keeps its capacity andn = 0.
Watch it step by step in Python Tutor
Open Python Tutor, pick C (gcc) and paste this short version of level 2, with both passes in main:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
double v[] = {6.5, 4.0, 9.5, 3.0};
int n = 4, k = 0;
for (int i = 0; i < n; i++)
if (v[i] >= 5) k++;
double *passed = malloc(k * sizeof(double));
if (passed == NULL) return 1;
int j = 0;
for (int i = 0; i < n; i++)
if (v[i] >= 5) passed[j++] = v[i];
printf("%d passed, the first one %.1f\n", k, passed[0]);
free(passed);
passed = NULL;
return 0;
}
What to watch step by step:
- During the first pass nothing appears on the heap: only
kchanges on the stack (it ends up as 2). - After the
malloc, a block of exactly 2 slots appears on the heap, not one more: that’s what the first pass was for. - In the second pass, watch
j: it only moves on when the grade is a pass, whileialways moves on. - After
free(passed), the block disappears, and withpassed = NULLthe arrow stops pointing anywhere.
Cheat sheet: dynamic memory in C
The essentials in 30 seconds
- Everything is in
<stdlib.h>. Sizes are in bytes:n * sizeof *v. mallocdoesn’t initialise;calloczeroes;reallocchanges the size keeping the data.- After every reservation, check for
NULL. reallocalways with a temporary pointer.- Every block, one owner and a single
free. Afterwards,p = NULL. - A function that returns an array reserves it with
malloc; the caller frees it.
Reserving and freeing
| I want… | Code | Result |
|---|---|---|
| n uninitialised doubles | double *v = malloc(n * sizeof *v); | Block of n × 8 bytes or NULL |
| n counters at zero | int *c = calloc(n, sizeof *c); | Block of n × 4 bytes at 0 or NULL |
| Go to m slots | double *tmp = realloc(v, m * sizeof *v); if (tmp) v = tmp; | Data kept; if it fails, v intact |
| Give the memory back | free(v); v = NULL; | Block freed, pointer switched off |
| Know how many slots I have | Keep it in a variable n | sizeof(v) is 8: useless |
Templates from the exercises
| Pattern | Key code | Result with the exercises’ data |
|---|---|---|
| Function returning a new array | double *r = malloc(n * sizeof *r); … return r; | raise_grades → 5.0 6.5 10.0 3.5 8.0 |
| Reserve just enough | Count → malloc(k * sizeof *r) → copy | 5 passes in 40 bytes |
| Growing array | if (n == cap) { tmp = realloc(v, 2 * cap * sizeof *v); … } | cap 1 → 2 → 4 → 8 with 7 grades |
| Filter in place | if (ok) v[j++] = v[i]; then realloc to j | 7 grades → 5 passes, cap 5 |
| Dynamic table | double **t = malloc(f * sizeof *t); + t[i] = calloc(c, sizeof **t); | Free the rows and then t |
Symptom and likely cause
| Symptom | Likely cause |
|---|---|
Segmentation fault when using the array | malloc/realloc returned NULL and it wasn’t checked, or you returned a local array |
heap-buffer-overflow (AddressSanitizer) | You reserved bytes instead of slots or went past index n - 1 |
free(): double free detected | Two free calls on the same block (two owners) |
detected memory leaks | A free is missing, or v = realloc(v, …) failed and lost the block |
| Weird numbers in new slots | malloc and realloc don’t initialise: use calloc or fill them in |
warning: function returns address of local variable | You return a stack array: reserve it on the heap |
Before handing in, ask yourself
- Does every
malloc,callocandreallocmultiply bysizeofand check forNULL? - Do all the
realloccalls use a temporary pointer? - Do I know who owns each block, and is there exactly one
freeper block on every path through the program? - Have I compiled with
-g -fsanitize=addressand does the program end without any report?
With this you’ve wrapped up dynamic memory. The next step in IC2 is structs and strings in C, where everything from today is used to store complete records.
Keep goingAll C (en) articlesDid this article help you? Here is how you can support SergioLearns →
