Laboratory Overview
What is new in this laboratory.
In the previous laboratory, you used structures, header files, source files, and separate compilation to solve a calendar problem. In this laboratory, you will build another important foundation: working with arrays whose size may be known either at compile time or only at runtime.
array โ pointer โ function parameter โ runtime size โ dynamic allocation โ struct result โ reusable library function
The goal is not only to allocate memory, but also to understand where memory lives, how long it exists, who is responsible for releasing it, and how to write functions that safely process arrays.
Learning Outcomes
- declare and use fixed-size arrays,
- explain why arrays passed to functions behave like pointers,
- use array size as a separate function parameter,
- understand what a Variable Length Array is and when it should be avoided,
- allocate and release memory using
malloc,calloc, andfree, - distinguish stack lifetime from heap lifetime,
- avoid common memory errors such as invalid
free, memory leaks, and returning local arrays, - write simple reusable functions for arrays of
long, - return a structure containing statistics calculated for an array,
- create
array.h, separate implementation files, object files, and a static librarylibarray.a.
How to work with this laboratory
Some code is shown, some code must be completed, and some code must be written independently.
| Label | Meaning |
|---|---|
| Shown | The code is provided because it introduces a new concept. Read it, run it, and understand it. |
| Complete | A partial solution is provided. You must fill in the missing parts. |
| Implement yourself | You should write the solution independently using patterns introduced earlier. |
| Self-check | Answer the question first. Then open the hidden answer and compare. |
exXX.cโ final solution for an exercise; these files are evaluated.demo_*.cโ demonstration programs provided in the laboratory; run them to understand a concept.test_*.cโ test programs for checking your own code or the library; they are not evaluated.
Task 0 โ Reminder and preparation
Step 1 โ Log in to the AGH UNIX server
ssh your_login@student.agh.edu.pl
Step 2 โ Go to the course directory
cd ~/I2PL
Step 3 โ Create the directory for Laboratory 7
mkdir lab7
cd lab7
Step 4 โ General rules
- Work only in
~/I2PL/lab7. - Compile with warnings enabled.
- Always check the result of
mallocandcalloc. - Every successful allocation must have a corresponding
free. - Do not use global variables.
gcc -Wall program.c -o program
Task 1 โ Fixed-size arrays
Step 1 โ Create the file
nano ex20.c
Step 2 โ Write a program using a fixed-size array
Declare an array of five long values, print all elements, and print the total size of the array in bytes.
You may use the following structure, but write the program yourself.
#include <stdio.h>
#define SIZE 5
int main(void) {
long tab[SIZE] = { 12, -4, 7, 0, 25 };
/* TODO: print all elements */
/* TODO: print sizeof(tab) */
/* TODO: print number of elements */
return 0;
}
sizeof(tab) gives the size of the whole array in bytes.
Task 2 โ Arrays as function parameters
Step 1 โ Create the file
nano ex21.c
Step 2 โ Study the shown function
#include <stdio.h>
void print_l(const long tab[], int size) {
for (int i = 0; i < size; ++i) {
printf("%ld ", tab[i]);
}
printf("\n");
}
int main(void) {
long tab[] = { 5, 10, 15, 20 };
int size = sizeof(tab) / sizeof(tab[0]);
print_l(tab, size);
return 0;
}
Step 3 โ Implement a variation yourself
Add one more function:
void print_reverse_l(const long tab[], int size);
The function should print the same array in reverse order.
long tab[] is adjusted to long *.
The function does not know how many elements the array has unless you pass the size separately.
Task 3 โ Variable Length Arrays
Step 1 โ Create the file
nano demo_vla.c
Step 2 โ Run the VLA experiment
#include <stdio.h>
int main(void) {
int n;
printf("Number of elements: ");
if (scanf("%d", &n) != 1 || n <= 0 || n > 1000) {
printf("Invalid size\n");
return 1;
}
long tab[n]; /* VLA: size known at runtime */
for (int i = 0; i < n; ++i) {
tab[i] = i * 10L;
}
printf("sizeof(tab) = %zu bytes\n", sizeof(tab));
for (int i = 0; i < n; ++i) {
printf("%ld ", tab[i]);
}
printf("\n");
return 0;
}
malloc will be the main technique for runtime-sized arrays.
Task 4 โ Memory lifetime: stack vs heap
Step 1 โ Create the demonstration file
nano demo_stack_heap.c
Step 2 โ Automatic arrays
#include <stdio.h>
void demo_stack(int n) {
int a[10]; /* fixed-size local array */
int b[n]; /* VLA */
printf("sizeof(a) = %zu\n", sizeof(a));
printf("sizeof(b) = %zu\n", sizeof(b));
}
int main(void) {
demo_stack(5);
return 0;
}
Step 3 โ Dynamic memory
#include <stdio.h>
#include <stdlib.h>
void demo_heap(int n) {
int *p = malloc(n * sizeof(*p));
if (p == NULL) {
printf("Allocation failed\n");
return;
}
printf("sizeof(p) = %zu\n", sizeof(p));
free(p);
p = NULL;
}
sizeof(p) gives the size of the pointer, not the size of the allocated block.
Step 4 โ Find the errors
For each example, identify the problem. Then open the answer.
Example 1 โ Invalid free
void f(int n) {
int a[n];
free(a);
}
Answer
a is a VLA with automatic storage. It was not allocated with malloc,
calloc, or realloc, so calling free(a) is invalid.
Example 2 โ Returning stack memory
int *f(int n) {
int a[n];
return a;
}
Answer
The function returns a pointer to a local array. After the function returns, the array no longer exists, so the returned pointer becomes invalid.
Example 3 โ Memory leak
void f(int n) {
int *p = malloc(n * sizeof(*p));
}
Answer
If malloc succeeds, the allocated memory is never released. The function should call
free(p) before it finishes, unless the pointer is returned to the caller and freed there.
Example 4 โ Wrong sizeof
int *p = malloc(10 * sizeof(p));
Answer
sizeof(p) is the size of the pointer, not the size of one int object.
Prefer one of the following forms:
malloc(10 * sizeof(*p));
malloc(10 * sizeof(int));
Task 5 โ Understanding sizeof
Step 1 โ Create the demonstration file
nano demo_sizeof.c
Step 2 โ Compare a pointer and the pointed object
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int x = 10;
int *p = &x;
printf("sizeof(p) = %zu\n", sizeof(p));
printf("sizeof(*p) = %zu\n", sizeof(*p));
return 0;
}
sizeof(p) gives the size of the pointer itself.sizeof(*p) gives the size of the object pointed to by p.
Step 3 โ Parentheses and readability
sizeof *p /* correct */
sizeof(*p) /* also correct, recommended in this course */
sizeof(*p) is easier to read and less confusing for beginners.
Step 4 โ Correct allocation pattern
long *tab = malloc(n * sizeof(*tab));
tab changes later.
Step 5 โ Self-check
What is wrong with the following allocation?
long *tab = malloc(n * sizeof(tab));
Answer
sizeof(tab) is the size of the pointer, not the size of one long object.
The allocation should use sizeof(*tab) or sizeof(long).
long *tab = malloc(n * sizeof(*tab));
long *tab = malloc(n * sizeof(long));
Task 6 โ Dynamic allocation with malloc and calloc
Step 1 โ Create the file
nano ex22.c
Step 2 โ Complete the program
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
printf("Number of elements: ");
if (scanf("%d", &n) != 1 || n <= 0) {
printf("Invalid size\n");
return 1;
}
long *tab = malloc(n * sizeof(*tab));
if (tab == NULL) {
printf("Allocation failed\n");
return 1;
}
/* TODO: fill the array with values */
/* TODO: print all values */
free(tab);
tab = NULL;
return 0;
}
Step 3 โ Repeat the experiment with calloc
Replace malloc with calloc and check the initial values of the array.
long *tab = calloc(n, sizeof(*tab));
Task 7 โ Utility functions for arrays
Step 1 โ Create the file
nano ex23.c
Step 2 โ Implement printing
Write:
void print_l(const long tab[], int size);
Step 3 โ Implement randomization
Write:
void randomize_l(long tab[], int size, long min, long max);
The function should fill the array with random values from the interval [min, max].
Step 4 โ Hint
tab[i] = min + rand() % (max - min + 1);
rand() % n may introduce slight bias.
<stdlib.h> for rand and <time.h> for time
if you initialize the random number generator with srand(time(NULL)).
Step 5 โ Random number initialization: two design variants
A library function should be easy to use. There are two reasonable ways to handle initialization of the random number generator.
Variant 1 โ explicit initialization by the caller
#include <stdlib.h>
#include <time.h>
int main(void) {
long tab[10];
srand((unsigned int) time(NULL));
randomize_l(tab, 10, -20, 20);
return 0;
}
srand once.
Variant 2 โ automatic initialization inside randomize_l
#include <stdlib.h>
#include <time.h>
void randomize_l(long tab[], int size, long min, long max) {
static int initialized = 0;
if (!initialized) {
srand((unsigned int) time(NULL));
initialized = 1;
}
for (int i = 0; i < size; ++i) {
tab[i] = min + rand() % (max - min + 1);
}
}
static variable remembers whether initialization has already been done.
Self-check: which variant should we use?
Both variants are valid. Variant 1 is more explicit and closer to standard C style.
Variant 2 is more convenient and demonstrates a useful role of a local static variable.
In this laboratory, you may choose one variant, but be consistent and document your decision
in array.h or in comments near randomize_l.
Task 8 โ Array statistics with a structure
Step 1 โ Create the file
nano ex24.c
Step 2 โ Study the structure
The structure stores several values calculated from one array.
typedef struct {
int size;
long min;
long max;
long range;
long sum;
double mean;
} ARRAY_STATS;
Step 3 โ Implement the function
Write the function below. Calculate all values in a single loop.
ARRAY_STATS statistics_l(const long tab[], int size);
Assume that size > 0.
Step 4 โ Starting hint
ARRAY_STATS stats;
stats.size = size;
stats.min = tab[0];
stats.max = tab[0];
stats.sum = tab[0];
Step 5 โ Test case
For this array:
long tab[] = { 4, -2, 10, 8 };
The expected values are:
size = 4
min = -2
max = 10
range = 12
sum = 20
mean = 5.00
Self-check: possible implementation
ARRAY_STATS statistics_l(const long tab[], int size) {
ARRAY_STATS stats;
stats.size = size;
stats.min = tab[0];
stats.max = tab[0];
stats.sum = tab[0]; /* first element already included */
for (int i = 1; i < size; ++i) {
if (tab[i] < stats.min) {
stats.min = tab[i];
}
if (tab[i] > stats.max) {
stats.max = tab[i];
}
stats.sum += tab[i];
}
stats.range = stats.max - stats.min;
stats.mean = (double) stats.sum / stats.size;
return stats;
}
min, max, and sum from the first element.
Then start the loop from index 1 to avoid processing the first element twice.
Task 9 โ Create array.h and separate implementation files
Step 1 โ Create the header file
nano array.h
#ifndef ARRAY_H
#define ARRAY_H
typedef struct {
int size;
long min;
long max;
long range;
long sum;
double mean;
} ARRAY_STATS;
void print_l(const long tab[], int size);
/*
* randomize_l fills the array with random values from [min, max].
* Choose one design:
* 1. call srand((unsigned int) time(NULL)) once in main before using it, or
* 2. initialize the generator inside randomize_l using a local static variable.
*/
void randomize_l(long tab[], int size, long min, long max);
ARRAY_STATS statistics_l(const long tab[], int size);
#endif
Step 2 โ Create print_l.c
nano print_l.c
Move only the implementation of print_l to this file.
#include <stdio.h>
#include "array.h"
void print_l(const long tab[], int size) {
/* TODO: paste your implementation here */
}
Step 3 โ Create randomize_l.c
nano randomize_l.c
Move only the implementation of randomize_l to this file.
#include <stdlib.h>
#include <time.h>
#include "array.h"
void randomize_l(long tab[], int size, long min, long max) {
/* TODO: paste your implementation here */
}
<time.h> is needed only if your implementation initializes the generator
with time(NULL) inside randomize_l.
Step 4 โ Create statistics_l.c
nano statistics_l.c
Move only the implementation of statistics_l to this file.
#include "array.h"
ARRAY_STATS statistics_l(const long tab[], int size) {
/* TODO: paste your implementation here */
}
bsort_l.c and isort_l.c will be added.
Step 5 โ Create a minimal program that uses the library
nano ex25.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "array.h"
int main(void) {
long tab[10];
/* Needed only if you chose Variant 1 for randomize_l. */
srand((unsigned int) time(NULL));
randomize_l(tab, 10, -20, 20);
print_l(tab, 10);
ARRAY_STATS stats = statistics_l(tab, 10);
printf("size = %d\n", stats.size);
printf("min = %ld\n", stats.min);
printf("max = %ld\n", stats.max);
printf("range = %ld\n", stats.range);
printf("sum = %ld\n", stats.sum);
printf("mean = %.2f\n", stats.mean);
return 0;
}
Task 10 โ Build a static library
Step 1 โ Compile each implementation file
gcc -Wall -c print_l.c
gcc -Wall -c randomize_l.c
gcc -Wall -c statistics_l.c
Step 2 โ Create the static library
ar rcs libarray.a print_l.o randomize_l.o statistics_l.o
Step 3 โ Compile the program with the library
gcc -Wall ex25.c -L. -larray -o ex25
./ex25
libarray.a.
Checklist before submission
- you can explain the difference between a fixed-size array, a VLA, and a dynamically allocated array,
- you know when memory is released automatically and when
freeis required, - you do not call
freeon local arrays or VLAs, - you do not return pointers to local arrays,
- you implemented
print_reverse_l, - you implemented
print_landrandomize_l, - you implemented
statistics_land tested it on known values, - you understand why the statistics loop starts from index
1, array.hcontains include guards, theARRAY_STATStype, and function declarations,print_l.c,randomize_l.c, andstatistics_l.ccontain separate implementations,ex25.cuses functions fromlibarray.a,libarray.ais created and used during compilation.