The memset function is a commonly used function in the C programming language library that is designed to fill a block of memory with a specified value.
For example, imagine you possess an array containing 8 characters, which occupies 8 bytes of memory upon its declaration. When this array is initialized, your computer reserves 8 bytes of memory on the heap within your system. This memory allocation follows a specific pattern and is not arbitrary. Randomization of memory allocation only takes place under certain circumstances, such as during processes like Address Space Layout Randomization, for specific security reasons, or through hexadecimal assignment of addresses ranging from 0x00 to 0x07. If you were to declare an array of arrays with each array containing 8 characters, it would result in a similar memory allocation process.
char array[8];
At first, the array's block memory holds unknown values when uninitialized. These values could be leftovers from previous programs or simply undefined memory blocks.
In a broader sense, adhering to best practices when dealing with C involves taking complete charge of all memory allocations within a program. Hence, rather than leaving these 8 bytes of memory unused, it is imperative to always initialize or populate the memory with a specified value in addition to a null byte ( \0 ). This approach guarantees complete management of the memory block occupied by the array you have defined. Refer to the diagram below for a visual representation of the concept being explained.
In simpler terms, your goal is to fill the functional segments of memory using memset. The memset function requires the following inputs. It receives three arguments: a pointer pointing to the memory block, an integer value, and a size_t which is essentially an unsigned integer capable of storing array indices.
void *s, int c, size_t
The initial parameter signifies a void pointer, as the memory space is either empty at the start or soon to be populated. The void type indicates that the pointer can access the memory without considering the specific data type.
The integer provided as the second parameter will be utilized for pointer allocation. It is crucial to understand that this parameter is initially accepted as an integer argument and then transformed into a character when filling memory. This conversion process guarantees that functions extract only the essential 8 bits from the integer parameter. Failing to perform this conversion could lead to unnecessary memory consumption.
The third argument indicates the specified size of the memory block requiring filling. It is crucial because the pointer alone cannot determine the amount of memory to fill accurately. Thus, including this parameter is essential to guarantee correct allocation size and prevent any memory leakage issues.
Return value of memset
The return type of memset by default is a void * (pointer without a specific type). When using memset, you may not receive any explicit output if you anticipate receiving the same input as the function operates. The function may only provide output after it completes its operation and returns a pointer with the identical memory address that was initially passed to the memset function.
How to use memset?
To incorporate memset into your program, the initial step involves including the string header file in your code, as demonstrated below.
#include <string.h>
Once you have added the string library to your program, you have all you need. This enables direct access to the memset function. For better clarity and simplicity, take a look at the following code excerpt which also includes two additional libraries such as <stdio.h> and <stdlib.h> (used for exit and printf functionalities).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char memory[8];
/* Fill the memory block with null bytes */
memset(memory, '\0', 8);
printf("%s\
", memory);
/* Fill the memory block with hashes */
memset(memory, '#', 8);
printf("%s\
", memory);
/* Fill the memory block with dollar signs */
memset(memory, '$', 8);
printf("%s\
", memory);
return (EXIT_SUCCESS);
}
When is memset used?
When coding in C, it is crucial to carefully manage memory allocation and initialization to ensure efficient and stable performance. If memory isn't initialized right away, functions like strcpy become necessary. Using strcpy correctly entails allocating a new memory block to store a string before manipulating it. Since this memory isn't initialized, it should be declared beforehand to prevent issues when using strcpy. In such scenarios, memset proves useful for either filling or clearing out memory blocks initially. To grasp the significance of using memset, let's examine the following example.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char src[7] = "Source";
char dest[7];
/* Initialize dest with null bytes for good practice */
memset(dest, '\0', sizeof(dest));
/* Before copying */
printf("String src before copy: %s\
", src);
printf("String dest before copy: %s\
", dest);
/* Copy src into dest */
strcpy(dest, src);
/* After copying */
printf("String src after copy: %s\
", src);
printf("String dest after copy: %s\
", dest);
return (EXIT_SUCCESS);
}
In most cases, the memset function is a valuable asset to keep in mind when dealing with memory allocation techniques in the C programming language. To put it simply, if you're unable to initialize it, resort to using memset.
Applications of memset
So far, you have explored various examples and scenarios where a specific memset function is used. However, to gain a deeper understanding, it is beneficial to examine multiple instances that showcase the practical application of memset. Below are some examples illustrating its diverse use cases.
Example 1
#include <stdio.h>
#include <string.h>
int main() {
char a[] = {"Hello from JournalDev"};
printf("a = %s\n", a);
printf("Filling the first 5 characters a with 'H' using memset\n");
memset(a, 'H', 5 * sizeof(char));
printf("After memset, a = %s\n", a);
return 0;
}
Output:
a = Hello from JournalDev
Filling the first five characters a with 'H' using memset
After memset, a = HHHHH from JournalDev
It is evident from the previous illustration as well as this instance how the memset function manages initialization. Within the provided code snippet, you can observe the interaction of memset with the variable specifying the length of the character array or string. Following the execution of the memset function, you will notice five instances of the letter H repeated within the specified string.
Example 2
#include <stdio.h>
#include <string.h>
int main() {
char a[] = {"Hello from JournalDev"};
printf("a = %s\n", a);
printf("Filling the last 5 characters a with 'H' using memset\n");
size_t a_len = strlen(a);
//Using an offset of (a + a_len - 5), so that we can
//fill the last 5 characters
memset(a + (a_len - 5), 'H', 5 * sizeof(char));
printf("After memset, a = %s\n", a);
return 0;
}
Output:
a = Hello from JournalDev
Filling the last five characters a with 'H' using memset
After memset, a = Hello from JournHHHHH
This instance mirrors the prior one with an engaging introduction. It is crucial to grasp the diverse applications of the memset function. In the previous illustration, solely the offset position gets populated. Conversely, in this scenario, the final five characters are filled with 'H', as the memory location or address initialization has already taken place.
Memset vs calloc vs naive iteration
There may be situations where utilizing memset for zeroing out arrays during initialization can be advantageous. In some cases, alternative methods such as naive approaches or employing the calloc function may present performance drawbacks. Certain complexities related to speed and memory allocation strategies can be challenging to grasp and put into practice. To gain insight into how memset addresses these issues, refer to the following code snippet to explore the benefits of using memset and compare it with calloc. Both functions can be executed on a Linux system by including the <time.h> header file.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
void* init_with_memset(int* arr, size_t num_locations) {
// Perform zero initialization with memset
// on an integer array
return memset(arr, 0, num_locations * sizeof(int));
}
void* init_with_calloc(int* arr, size_t num_locations) {
arr = calloc(num_locations, sizeof(int));
return arr;
}
void* init_with_iteration(int* arr, size_t num_locations) {
// Naive unoptimized iteration using array indexing
for (int i=0; i<num_locations; i++) {
arr[i] = 0;
}
return arr;
}
int main() {
// Set the array to -1 initially
int arr[2560];
for (int i=0; i<2560; i++)
arr[i] = -1;
clock_t start_time, end_time;
double total_time;
start_time = clock();
// 1000 locations
init_with_memset(arr, 1000);
end_time = clock();
total_time = (double) (end_time - start_time);
total_time = total_time / CLOCKS_PER_SEC;
printf("Time for memset() = %.6f seconds\n", total_time);
start_time = clock();
// 1000 locations
init_with_calloc(arr, 1000);
end_time = clock();
total_time = (double) (end_time - start_time);
total_time = total_time / CLOCKS_PER_SEC;
printf("Time for calloc() = %.6f seconds\n", total_time);
start_time = clock();
// 1000 locations
init_with_iteration(arr, 1000);
end_time = clock();
total_time = (double) (end_time - start_time);
total_time = total_time / CLOCKS_PER_SEC;
printf("Time for naive iteration = %.6f seconds\n", total_time);
return 0;
}
Output:
Time for memset() = 0.000002 seconds
Time for calloc() = 0.000005 seconds
Time for naive iteration = 0.000006 seconds
In the previous example, it's evident and clear that the memset function outperforms both the standard iteration method and calloc by a factor of three in terms of speed. This superior performance is attributed to its optimization according to the underlying C compiler architecture, resulting in automatic speed improvements.
Conclusion
As commonly mentioned, if you fail to initialize, consider using memset. This adage typically highlights the benefits of memset in comparison to alternatives such as calloc or manual iteration. Within this guide, you learned strategies for addressing uninitialized memory areas when working with arrays. The challenges and resolutions presented here underscore the significance of utilizing memset for efficient memory allocation in languages like C.