The Perils of the memset() Function

While researching the upcoming — and significant — C23 version of the C programming language, I learned something surprising: The memset() function will be deprecated. It effectively does nothing when used in the C23 standard. The reason makes a lot of sense.

I wrote about the memset() function in a Lesson from 2021. What it does is to initialize a chunk of memory (a buffer) to a specific value. Here is the man page format:

void *memset(void *b, int c, size_t len);

Defined in the string.h header file, the memset() function writes len bytes of value c to the buffer at b. As I wrote last year, memset() initializes a buffer, ensuring that its contents are zero or whatever value you set. Even so, this function is burdened with a number of issues.

First, no guarantee is available to prove that buffer b is len bytes in size. So the function could overwrite memory if you’re not careful.

Second, compiler optimization often nullifies the function, rending the buffer uninitialized. This optimization happens all the time, so unless you disable it or go to the effort to confirm that the program actually writes data to the entire buffer, who knows whether the buffer is initialized?

One of the major issues here is that network programming often uses the memset() function to zero out important structures. If such a structure isn’t “zeroed out,” network functions behave improperly.

An alternative, secure version of the function is available, memset_s(). Also defined in the string.h header, this function isn’t part of the standard C library. Here is the man page format:

errno_t memset_s(void *s, rsize_t smax, int c, rsize_t n);

Argument s is the buffer to overwrite and smax is the buffer’s size. Argument c is the character (unsigned char) to write, n is the number of characters, which must be less than smax. The function returns zero upon success, otherwise an error code is returned as defined in its man page. The error code is not an errno value. Unlike the memset() function, memset_s() isn’t optimized by the compiler.

Here is an update to the code I wrote last year to test the memset() function, but using memset_s() instead.

2022_03_12-Lesson.c

#include <stdio.h>
#include <string.h>

int main()
{
    char buffer[BUFSIZ];

    memset_s(buffer, BUFSIZ, '\0', BUFSIZ);
    puts("Buffer initialized");

    return(0);
}

The two changes required are first, of course, to change memset() to memset_s(). The second change is to add the function’s second argument, the buffer size. In this code I use the BUFSIZ constant, which appears as both the second and fourth arguments.

Here is the boring sample run:

Buffer initialized

In my older post, I also mentioned the bzero() function, which works like memset() but always uses the zero value to fill the buffer. The problem with bzero() is that it’s non-standard. As I wrote in the original post, it’s still okay to write your own function to initialize a chunk of memory. Especially if your compiler lacks the memset_s() function, this is the route I would recommend taking.

In next week’s Lesson, I cover the manual method to initialize a structure to zero.

2 thoughts on “The Perils of the memset() Function

  1. “First, no guarantee is available to prove that buffer b is len bytes in size. So the function could overwrite memory if you’re not careful.”

    Anyone know if valgrind will catch this?

    “Second, compiler optimization often nullifies the function, rending the buffer uninitialized. This optimization happens all the time, so unless you disable it or go to the effort to confirm that the program actually writes data to the entire buffer, who knows whether the buffer is initialized?”

    gcc and possibly other compilers have a number of optimization options (which I have to admit I’ve never investigated fully, or at all actually!) so presumably you can use one of these. However, doing so might mess up optimizations you actually want.

    In C in a Nutshell (O’Reilly) memset_s is listed as:

    errno_t memset_s(void* dest, rsize_t destmax, int c, rsize_t n); (C11)

    which is the same as you have shown but with a couple of different argument names.

    bzero isn’t listed so is presumably still non-standard.

  2. Interesting. Again, memset_s() isn’t a standard function. I’m hoping that in C23 they make it and the rest of the _s functions standard.

Leave a Reply