Module 4: Pointer and Struct - Algoritma-dan-Pemrograman-ITS/DasarPemrograman GitHub Wiki
Every variable, function, struct, or other object created in the program has its own location in memory. The allocation of each variable is stored in a specific memory address.
Example:
There is a variable named var. To find the memory address of a variable, use the address-of (&) operator in front of the variable name.
int var = 5;
printf("%d\n", var);
printf("%p\n", &var);Output:
5
0x7fffdeb3ed84
The output may differ for each execution.
0x7fffdeb3ed84 is the memory address of the var variable.
A pointer is a special variable that holds a memory address instead of a value like regular variables.
A pointer variable declaration uses the * operator between the data type and the variable name.
int *ptr;or
int* ptr;Both methods are valid.
The ptr variable above is a pointer variable of type int. The pointer variable holds a memory address. The initialization of the pointer variable must be a memory address, it can be from other variables or dynamic allocation.
int var = 55;
int *ptr = &var; // Inisialization uses the address of varImproper initialization will result in an error or undefined behavior.
// ERROR
int *ptr = 5;
// UNDEFINED BEHAVIOUR
int *ptr2 = 0x7fffdeb3ed84;Pointer variable assignment is not the same as pointer initialization.
int var, *ptr;
var = 55;
ptr = &var; // assignment to a pointer variable using the address of varWhen assigning a pointer variable, we don’t need to use the * symbol in front of the variable name. It's different from when the declaration where we need to tell the compiler that the variable is a pointer variable.
The dereference operator uses the same symbol as the multiplication operator symbol, which is * (asterisk symbol). However, their functions are very different. The dereference operator is used to access the value pointed from a pointer variable.
To access the value of a pointer variable, use the dereference operator in front of the pointer variable’s name.
int var = 55;
int *ptr = &var;
printf("%d\n", *ptr);
*ptr = 20;
printf("%d\n", *ptr);
printf("%d\n", var);Output
55
20
20What is the output of the below program?
#include <stdio.h>
int main(void)
{
int x, y, z;
int *ptr1, *ptr2;
x = 5;
y = 6;
ptr1 = &x;
ptr2 = &y;
z = *ptr1 + *ptr2;
printf("%d\n", z);
return 0;
}A pointer variable can also point to other pointer variables. This is known as a double pointer (pointer to pointer). To declare a double pointer variable, use two symbols *. The most common use of double pointer variables is to dynamically create two-dimensional arrays.
int **dbPtr;The variable dbPtr above stores the memory addresses of other pointer variables.
#include <stdio.h>
int main(void)
{
int var = 23;
int *ptr = &var;
int **dbPtr = &ptr;
printf("%d\n", **dbPtr);
return 0;
}We already know that an array is a collection of data arranged sequentially. Because they are arranged sequentially, the memory addresses of each element of the array are also arranged sequentially.
How to find out the memory address of an array?
#include <stdio.h>
int main()
{
int arr[5] = {1, 2, 3, 4, 5};
int i;
for (i = 0; i < 5; ++i) {
printf("&arr[%d] => %p\n", i, &arr[i]);
}
printf("Address of arr => %p\n", arr);
return 0;
}Output
&arr[0] => 0x7fffe85f0520
&arr[1] => 0x7fffe85f0524
&arr[2] => 0x7fffe85f0528
&arr[3] => 0x7fffe85f052c
&arr[4] => 0x7fffe85f0530
Address of arr => 0x7fffe85f0520
It can be seen that the address of &arr[0] is the same as the address of arr. It is also known that the array name points to the first element of the array. Because &arr[0] = arr, it can be concluded that arr[0] = *arr, or the value of the first element can be accessed by *arr or *(arr + 0).
arr[0] = *(arr + 0)
arr[1] = *(arr + 1)
arr[2] = *(arr + 2)
.
.
etc.
Conclusion: The array name refers to the memory address of the first element in the array. Based on that, we can do access array elements by calling the array name + the index.
#include <stdio.h>
int main()
{
int arr[5] = {1, 2, 3, 4, 5}, i;
int *ptr = arr;
for (i = 0; i < 5; ++i) {
printf("%d ", *(ptr+i));
}
return 0;
}Output
1 2 3 4 5
In the previous module, we have learned that function can accept arguments as input. But we only have been passing variables to a function by value. There is another way to pass variables to functions.
Pass by Value means that when we pass an argument to a function, the value of the argument will be copied to the variable that is in the function parameter. Because only its value is accepted by the function, changes which occur in the function parameter variable will not affect the original variable.
Example:
#include <stdio.h>
void change(int a, int b)
{
a = a + 5;
b = b + 5;
}
int main()
{
int x = 10, y = 6;
change(x, y);
printf("%d %d\n", x, y);
return 0;
}The values of the x and y variables do not change because the passing method used is Pass by Value..
Unlike the previous one, as the name implies, the Pass by Address method means that the argument passed to the function parameter is the variable memory address. All changes that occur in these variables will directly affect the original variables. This happens because the argument is a memory address.
#include <stdio.h>
void change(int *a, int *b)
{
*a = *a + 5;
*b = *b + 5;
}
int main()
{
int x = 10, y = 6;
change(&x, &y);
printf("%d %d\n", x, y);
return 0;
}Since the parameter accepts a memory address, the parameter variable must be a pointer.
Passing arrays as function parameters can also be done with pointers. Any changes to the array will also affect the original array.
#include <stdio.h>
void printArr(int *arr)
{
// ...
// ...
}
int main()
{
int num[5] = {1, 2, 3, 4, 5}, i;
printArr(num);
// ...
// ...
return 0;
}In C language, struct is a derived data type or it can be called as user defined data type which can collect several variables under one name. Unlike arrays which can only store elements of the same data type, structs can collect elements with different data types.
Example:
Look at the picture above. Mahasiswa is an entity in which there are attributes, namely:
- Nama
- NRP
- Umur
- IPK
- Semester
- Status
These attributes are members of the Mahasiswa struct.
Like variables, structs must be declared before it can be used. The struct declaration uses the following syntax.
struct <struct_name> {
<member_data_type> <member_name>;
<member_data_type> <member_name>;
<member_data_type> <member_name>;
.
.
.
};Below is an example of a struct declaration based on the Mahasiswa case above.
struct Mahasiswa {
char nama[100];
char nrp[20];
int umur;
double ipk;
int semester;
int status;
};Once declared, a struct will become a new data type. So in this case, the Mahasiswa struct here becomes a new data type with the members being nama, nrp, umur, ipk, semester, and status. Here is how we create struct variables.
struct <struct_name> <variable_name>;Example:
struct Mahasiswa mhs1;
struct Mahasiswa mhs2;The example above shows that there are two variables mhs1 and mhs2 of type struct Mahasiswa.
How do we access the members of the struct variables that have been created? To access the members of a struct, use the dot operator (.) after the variable name.
<variable_name>.<struct_member_name>Example:
mhs1.umur = 19;
mhs1.semester = 3;
mhs2.umur = 20;
mhs2.semester = 5;Sample program to demonstrate Struct:
#include <stdio.h>
#include <string.h>
struct Mahasiswa {
char nama[100];
char nrp[20];
int umur;
double ipk;
int semester;
int status;
};
int main(void)
{
struct Mahasiswa mhs1;
strcpy(mhs1.nama, "Ahmad");
strcpy(mhs1.nrp, "05111940000012");
mhs1.umur = 18;
mhs1.ipk = 3.94;
mhs1.semester = 3;
mhs1.status = 1;
printf("Nama\t: %s\n", mhs1.nama);
printf("NRP\t: %s\n", mhs1.nrp);
printf("Umur\t: %d\n", mhs1.umur);
printf("IPK\t: %.2lf\n", mhs1.ipk);
printf("Sem\t: %d\n", mhs1.semester);
printf("Status\t: %s\n", (mhs1.status == 1 ? "Active" : "Not Active"));
return 0;
}We can also create arrays with the struct data type. The method is exactly the same as most array declarations.
#include <stdio.h>
struct Point {
int x, y;
};
int main()
{
struct Point arr[3];
arr[0].x = 2, arr[0].y = 3;
arr[1].x = 5, arr[1].y = 3;
arr[2].x = 2, arr[2].y = 8;
printf("%d %d\n", arr[0].x, arr[0].y);
printf("%d %d\n", arr[1].x, arr[1].y);
printf("%d %d\n", arr[2].x, arr[2].y);
return 0;
}In a C program, memory location is divided into several segments, namely:
- Code/Text Segment: stores the program code (instructions).
- Data Segment: stores global and static variables that are initialized.
- BSS Segment: stores global and static variables that are uninitialized.
- Heap Segment: stores dynamically allocated memory during program execution.
- Stack Segment: stores local variables and function call information.

In our previous modules, whenever we declare a variable, it's usually stored in the stack segment or the data/BSS segment, while all of our code is stored in the code/text segment.
#include <stdio.h>
int a = 10; // 'a' is stored in the data segment
char b; // 'b' is stored in the BSS segment
void function() {
static int c = 20; // 'c' is stored in the data segment
static char d; // 'd' is stored in the BSS segment
int e; // 'e' is stored in the stack segment
long long int f[1000] = {0}; // 'f' is stored in the stack segment
}
// All of this instructions are stored in the code/text segmentNotice how the heap segment is not mentioned in the code above, it is because the heap segment is exclusively used for when we explicitly allocate memory during program execution. This is known as dynamic memory allocation.
Dynamic memory differs from static memory allocation in many ways, such as:
| Aspect | Static Memory Allocation | Dynamic Memory Allocation |
|---|---|---|
| Speed | Generally faster | Generally slower |
| Size Limitations | Limited by initialized stack size (usually smaller) | Limited by system memory (usually larger) |
| Memory Size | Fixed at compile time (i.e. before the program is executed) | Can be adjusted at runtime (i.e. while the program is running) |
| Memory Lifetime | Automatically managed by the compiler | Must be manually managed by the programmer |
| Memory Allocation | Done automatically when variables are declared | Done explicitly through library functions |
| Security | Less prone to memory leaks | More prone to memory leaks if not managed properly |
Dynamic memory allocation is done using functions provided in the stdlib.h library, namely:
| Function | Description | Return Value |
|---|---|---|
malloc(size_t size) |
Allocates a block of memory the size of size The content of the allocated memory may contain garbage values. |
Pointer to the allocated memory block or NULL if the allocation fails. |
calloc(size_t num, size_t size) |
Allocates memory for an array of num elements, each of them size bytes long, and initializes all bytes in the allocated storage to zero. |
Pointer to the allocated memory block or NULL if the allocation fails. |
realloc(void *ptr, size_t new_size) |
Changes the size of the memory block pointed to by ptr to new_size bytes. The content will be unchanged in the range from the start of the region up to the minimum of the old and new sizes. If the new size is larger, the added memory will not be initialized. |
Pointer to the reallocated memory block or NULL if the allocation fails. |
free(void *ptr) |
Deallocates the memory previously allocated by a call to malloc, calloc, or realloc. This function does not return a value. |
None |
Both malloc and calloc can be used interchangeably to allocate memory. However, there are some key differences between the two functions :
When initializing memory, malloc only allocates memory without clearing previous data, meaning that the allocated memory may contain garbage values.
When initializing memory, calloc allocates memory and purposely initializes all bytes to zero.From a naïve perspective, calloc should always be preferred over malloc, since it guarantees that the allocated memory is initialized to zero. However, calloc is generally slower than malloc due to the additional step of initializing memory.
A proper use of dynamic memory allocation in C involves the following steps:
- Include the
stdlib.hlibrary. - Allocate memory using
mallocorcalloc. - Use the allocated memory.
- Resize the allocated memory using
reallocif necessary.
- Resize the allocated memory using
- Free the allocated memory using
freeto avoid memory leaks.
A simple example of using dynamic memory allocation in C:
#include <stdio.h>
#include <stdlib.h> // Step 1: Include the stdlib.h library
int main() {
// Step 2: Allocate memory for an array of 5 integers
int* a = (int*) malloc(5 * sizeof(int));
char* b = (char*) calloc(10, sizeof(char));
if (a == NULL) {
printf("Memory allocation failed\n");
return 1; // Exit if memory allocation fails
// Return 1 indicates an error occurred
}
if (b == NULL) {
printf("Memory allocation failed\n");
free(a); // Free previously allocated memory before exiting
return 1; // Exit if any of the memory allocation fails
}
// Note: it is good practice to never check both pointers in the same if statement
// the reason will be explained in the Common Pitfalls section
// Step 3: Use the allocated memory
for (int i = 0; i < 5; i++) {
a[i] = i + 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", a[i]);
}
// Step 4: Free the allocated memory
free(a);
return 0;
}When using dynamic memory allocation, there are several common pitfalls that every programmer should be aware of:
-
Memory Leaks: memory leaks are memory segments that are no longer needed but are not released back to the system. This can lead to increased memory usage as the program may end but the memory is still allocated/in use, leading to parts of the whole memory being unusable. Always ensure that every
malloc,calloc, orrealloccall has a correspondingfreecall.
int main() {
int* a = (int*)malloc(5 * sizeof(int));
int* b = (int*)malloc(10 * sizeof(int));
if (a == NULL || b == NULL) { // Incorrect check
printf("Memory allocation failed\n");
// In this situation, which memory should be freed?
// our program should be able to allocate memory for both a and b
// but if one of them fails, we don't know which one is failing
free(a);
free(b);
return 1;
}
// Missing free(a); leads to memory leak
return 0;
}-
Dangling Pointers: Accessing memory that has already been freed can lead to undefined behavior. Undefined behaviours may cause unpredictable results, crashes, or security vulnerabilities. Always set pointers to
NULLafter freeing them to avoid dangling pointers.
int main() {
int* a = (int*)malloc(5 * sizeof(int));
// ... some operations on 'a'
free(a);
// ... some operations on 'a' (dangling pointer)
return 0;
}- Double Free: Attempting to free the same memory block more than once can lead to an undefined behavior, program crashes, or corruption of the memory heap. Always ensure that a pointer is only freed once.
int* a = (int*)malloc(5 * sizeof(int));
// ... some operations on 'a'
free(a);
free(a); // Double free error-
Null Pointer Dereference: Always check if the pointer returned by
malloc,calloc, orreallocisNULLbefore using it.
int* a = (int*)malloc(5 * sizeof(int));
a[5] = 10; // a might be NULL, the program is trying to access an invalid memory location
// this can cause a segmentation fault error or a crash
free(a);- Buffer Overflow: Allocating insufficient memory for a data structure can lead to buffer overflows, which can cause security vulnerabilities and program crashes.
char *buffer = (char *)malloc(10 * sizeof(char));
if (buffer == NULL) {
printf("Memory allocation failed\n");
return 1;
}
// Attempt to copy a string longer than the allocated buffer
// this will cause a heap-based buffer overflow
strcpy(buffer, "This is a string that is clearly longer than 10 bytes.");
printf("Buffer content: %s\n", buffer); // Undefined behavior --> error or crash
free(buffer); // Free the allocated memory
}Implement a function named add() that takes 3 integer parameters, where the last parameter stores the result of the addition of the first two variables.
Function call example:
int a = 1;
int b = 2;
int c;
add(a, b, &c);
printf(“%d”, c);Output:
3
Create a struct that stores the final exam grades for N-amount of freshmen. Data for each freshman is recorded, containing name, grades for Mathematics, Science, Bahasa Indonesia, and English. After that, create a function that inserts a list of final exam grades and shows all data according to the student names.
P.S.: insert order is Mathematics, Science, Bahasa Indonesia, English. Below is sample input-output. 4 denotes the number of students to be inserted. The number 3 in the end denotes the amount of students requested to be shown.
Sample Input
4
Hope
100
90
20
90
Ricky
80
70
80
90
Maden
100
100
100
100
Tenten
90
80
99
100
3
Maden
Dennis
Tenten
Sample Output
Nilai Maden
Matematika : 100
IPA : 100
Bahasa Indonesia : 100
Bahasa Inggris : 100
Nilai Dennis tidak ditemukan
Nilai Tenten
Matematika : 90
IPA : 80
Bahasa Indonesia : 99
Bahasa Inggris : 100
Create a function named reverse() that reverses an array of integer using pointer. The function is expected to be used like the snippet below.
int arr[5]
.
.
//input
reverse(arr, 5);
.
.
//print isi arrSample Input:
5
8 4 2 3 1
Sample Output:
1 3 2 4 8