Protecting the world

from criminally bad code

memset

Synopsis

#include <string.h>

void* memset(void* s, int c, size_t n);

Description

memset fills a block of raw memory with a given byte value.

Pitfalls

Initialisation of objects using memset is not necessarily portable

A common use of memset is to initialise an object (typically a structure) such that numbers are set to zero and pointers are set to null. An advantage of this method is that it can be used when a full description of the structure to be initialised is not available. For example, POSIX requires that a sockaddr_in6 be zeroed before it is used, but does not give a full list of all of the fields it might contain.

The use of memset for this purpose makes the assumption that a bit pattern of all zeros corresponds to a numeric value of zero or a pointer value of null. This is true for most types in most implementations of C, but is not a requirement of the ISO standard. The general rule is that the representation of a type is unspecified unless there is specific provision to the contrary [1]. Types that can be assumed to have the desired behaviour are:

  • the character types (char, unsigned char[2] and signed char[3]);
  • any exact-width integer types provided by <stdint.h>[4];
  • the types float and double, but only if the implementation has defined the macro __STDC_IEC_559__ to declare that it conforms to the IEC 60559 floating-point arithmetic annex[5];
  • structure and union types composed entirely of members that can be safely initialised using memset[6]; and
  • array types composed of members that can be safely initialised using memset[7].

Initialisation of other types using memset may work for specific implementations of C but cannot be considered safe in general. A portable alternative is to use an initialiser as described below.

Interchanging the second and third arguments

A common mistake when using memset is to pass the second and third arguments in the wrong order. The compiler may be able to catch this in some cases (such as when the third argument is constant and zero) but not in general. The only way to reliably detect errors of this type is by inspection.

Passing sizeof(s) in place of sizeof(*s) as the third argument

Another common mistake is to pass a pointer as the first argument and the size of that pointer as the third argument, when the size that should be passed is that of the object to which the pointer refers. For example, the following call to memset is correct in the case where s is an array:

char s[8];
memset(s,0,sizeof(s));

but not in the case where s is a pointer:

char r[8];
char* s=r;
memset(s,0,sizeof(s));

This class of error has the potential to be quite insidious because it can be triggered by a code change some considerable distance from the site of the memset call.

Alternatives

Using an initialiser

If the type in question is an aggregate POD type, and the intent is to set pointers to null and numbers to zero, then this can be achieved in a fully portable manner using an initialiser:

struct foo_type foo={0};

The way this works is that:

  • If the first member is a scalar type then it is initialised as if by =0.
  • If the first member is an aggregate or a union then it is initialised as if by ={0}[8].
  • Any remaining members are initialised as if they had static storage duration and no initialiser.[9].

An initialiser of =0 always corresponds to null (for pointers) or zero (for numbers), even if these values have a non-zero internal representation.

A variation on this technique can be used when the object must be allocated dynamically:

static struct foo_type init_foo={0};
struct foo_type* foo=(struct foo_type*)malloc(sizeof struct foo_type);
if (!foo) die("failed to allocate memory for foo");
*foo=init_foo;

The same effect would be achieved if the initialiser could be reduced to the empty list, but ISO C does not allow this[10].

Using bzero

In the case where c==0 a historically acceptable alternative to memset would have been to use bzero. Although this function has never been part of the standard C library, it was required by SUSv2 and SUSv3 and is still provided by many implementations.

bzero was removed in SUSv4 after having been deprecated in SUSv3. It is not recommended for use in new code, and should removed from existing code if portability is a requirement.

Notes

  • [1] C99 §6.2.6.1 ¶1
  • [2] Implied by C99 §6.2.6.1 ¶3 and §5.2.4.2.1 ¶2
  • [3] Implied by C99 §6.2.6.2 ¶5
  • [4] Implied by C99 §7.18.1.1 ¶1,2
  • [5] Implied by C99 §F.2 ¶1
  • [6] Implied by C99 §6.2.6.1 ¶6
  • [7] Implied by C99 §6.2.5 ¶20
  • [8] C99 §6.7.8 ¶20
  • [9] C99 §6.7.8 ¶21
  • [10] C99 §6.7.8 ¶1

Portability

  •  C90
  •  C99
  •  C++98
  •  C++11

In C++, use of the header <string.h> is deprecated in favour of <cstring>.

Further reading