A Generic SWAP() Macro in C

Paul J. Lucas - Aug 8 - - Dev Community

Introduction

C++ has std::swap(), but there is no standard equivalent in C. If you’re using C23, implementing SWAP() is trivial:

#define SWAP(A,B) \
  do { auto _tmp = (A); (A) = (B); (B) = tmp; } while (0)
Enter fullscreen mode Exit fullscreen mode

For more about Preprocessor macros, and why the do ... while is a good idea in particular, see here.

That was easy. So we’re done, right? But what if you’re not using C23 and therefore can’t use auto?

A Pre-C23 SWAP()

Since C prior to C23 doesn’t have either auto (or typeof), there’s no way to declare a variable that’s the type of some other variable or expression.

Yes, both gcc and clang support __auto_type and typeof as extensions and you can use them if you want; but I prefer to use either standard C or at least have a way to fall back to standard C if certain features aren’t available.

Instead, we can just declare a char array that’s the right size and use it for temporary storage. Of course then we can’t simply use = to do assignments, but must instead use memcpy():

#define SWAP(A,B) do {               \
  char _tmp[ sizeof((A)) ];          \
  memcpy( _tmp, &(A), sizeof((A)) ); \
  (A) = (B);                         \
  memcpy( &(B), _tmp, sizeof((B)) ); } while (0)
Enter fullscreen mode Exit fullscreen mode

You might think calling memcpy() is less efficient than an assignment. The thing is that memcpy() is kind of special in that the compiler does special optimization for it — enough such that the generated code is as efficient.

Alignment

The problem with the above macro is that _tmp isn’t guaranteed to be properly aligned for an object of type A. Normally, you could use _Alignas, but that won’t work here because you’d need to say _Alignas(typeof(A)) and we can’t use typeof.

Even though _Alignas can alternatively take an expression, you can’t use _Alignas(A) either because that does not mean “align to the type of the expression A.” Instead it means “align to the value of the integral constant expression A” — and since A isn’t a constant expression, it won’t work.

Prior to C11 and the addition of _Alignas, the common trick to create an aligned char array was to enclose it within a union:

#define SWAP(A,B) do {                                        \
  union { char buf[ sizeof((A)) ]; max_align_t align; } _tmp; \
  memcpy( _tmp.buf, &(A), sizeof((A)) );                      \
  (A) = (B);                                                  \
  memcpy( &(B), _tmp.buf, sizeof((B)) ); } while (0)
Enter fullscreen mode Exit fullscreen mode

Including a member of type max_align_t will force the union (and the char array within it) to be properly aligned for any type.

Safeguards

We can improve SWAP() a bit by at least ensuring that A and B have the same size:

#define SWAP(A,B) do {                                        \
  static_assert( sizeof((A)) == sizeof((B)),                  \
    "SWAP() arguments must have same size" );                 \
  union { char buf[ sizeof((A)) ]; max_align_t align; } _tmp; \
  memcpy( _tmp.buf, &(A), sizeof((A)) );                      \
  (A) = (B);                                                  \
  memcpy( &(B), _tmp.buf, sizeof((B)) ); } while (0)
Enter fullscreen mode Exit fullscreen mode

Ideally, you want to ensure that A and B have the same type, not just size, but there’s unfortunately no way to do that without typeof.

Safeguard for C23’s SWAP()

If you are using C23, you can improve SWAP() by ensuring A and B have the same type (since typeof and typeof_unqual are available):

#define SWAP(A,B) do {                            \
  static_assert( IS_SAME( typeof(A), typeof(B) ), \
    "SWAP() arguments must have same type" );     \
  auto _tmp = (A); (A) = (B); (B) = _tmp; } while (0)
Enter fullscreen mode Exit fullscreen mode

where IS_SAME() uses _Generic and is:

#define IS_SAME(T,U)      \
  _Generic( *(T*)0,       \
    typeof_unqual(U): 1,  \
    default         : 0   \
  )
Enter fullscreen mode Exit fullscreen mode

The *(T*)0 is needed to convert T (a type) into an expression required by _Generic. (Reminder: the expression isn’t evaluated so it doesn’t matter that it’s dereferencing a null pointer.)

The typeof_unqual(U) is necessary to remove qualifiers, otherwise it would never match if U had qualifiers. (Reminder: _Generic discards qualifiers from the type of the controlling expression.)

Conclusion

Of course SWAP() isn’t really necessary: you can always open-code a swap manually every time; but having it makes things a little nicer regardless.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .