3
\$\begingroup\$

Properties:

  • It evaluates each of A and B only once (sizeof is special, as it doesn't evaluate its arguments, except for VLAs, which this solution would not work for).
  • It has a compile-time check for the correct sizes, and prints a nice, user-friendly error message if the sizes are not the same.
  • It has a compile-time check for compatible types.
  • It has no naming issue with a hidden variable.
  • The size of the temporary variable is computed at compile time, so the compound literal is not a dynamic array.
  • It does not rely on VLAs, so it is more portable.
  • It does not use declare any local variables.

Drawbacks:

  • It would not work for variables that are declared with the register storage class. All the folks using register variables in tight loops would not be looking for an extra function call anyway.

  • It would not work for VLAs, because:

    1. The language prohibits it:

    6.5.2.5 Compound literals

    Constraints

    1 The type name shall specify an object type or an array of unknown size, but not a variable length array type.

    1. static_assert expects a constant expression.
  • It would not work for identical variables (one should not be attempting to do that).

Code:

#include <assert.h>
#include <string.h>

/**
 * Like C11's _Static_assert() except that it can be used in an expression.
 *
 * EXPR - The expression to check.
 * MSG  - The string literal of the error message to print only if EXPR evalutes
 *        to false.
 *
 * Always return true. */
#define STATIC_ASSERT_EXPR(EXPR, MSG)   \
    (!!sizeof( struct { static_assert ( (EXPR), MSG ); char c; } ))

void util_memswap(size_t psize,
                  void *restrict p1, 
                  void *restrict p2)
{
    unsigned char *a = p1;
    unsigned char *b = p2;
    unsigned char tmp;

    while (psize--) {
        tmp = *a;
        *a++ = *b;
        *b++ = tmp;
    }
}

#define SWAP(A, B)                                                         \
    util_memswap((sizeof *(1 ? &(A) : &(B))                                \
        * STATIC_ASSERT_EXPR(sizeof (A) == sizeof (B),                     \
        "Arguments of SWAP() must have same size and compatible types.")), \
        &(A),                                                              \
        &(B))

Usage:

int main(void)
{
    int a = 10;
    int b = 20;

    SWAP(a, b);
    assert(a == 20);
    assert(b == 10);

    char s[10] = "hello";
    char t[10] = "world";
    
    SWAP(s, t);
    assert(strcmp(s, "world") == 0);
    assert(strcmp(t, "hello") == 0);
}

Another version of SWAP() can be used that would also work with VLAs. For instance, the original version of @null worked with VLAs too (and when a and b are same, as it uses memmove()):

#define SWAP_GENERIC(A, B) \
    util_memswap(sizeof *(1 ? &(A) : &B), &(A), &(B))

Although this does not have a compile-time check for dissimilar sizes.

References:

I have looked at a myriad of implementations and StackOverflow answers and built on the work of others. Credit where it is due (you'd also find lengthy explanations of the constructs in the code above here):

Review Request:

  • Anything. Everything. Problems? Is it robust enough? Can we make it more robust?

  • Most importantly, the nice error message is only printed when the sizes are different, but not when the types are different. Although the code fails to compile on a type-mismatch, and the compiler prints diagnostic messages about it, they are not very nice. How can I remedy this?

  • Is the swap_internal() function a candidate for C23's [[reproducible]] attribute specifier sequence? What are the [[reproducible]] and [[unsequenced]] attributes in C23, and when should I use them? says that memcpy() can be reproducible.

\$\endgroup\$
11
  • \$\begingroup\$ "sizeof is special, as it doesn't evaluate its argument" --> Hmm, sure about that? \$\endgroup\$ Commented Jun 7 at 19:18
  • \$\begingroup\$ @chux-ReinstateMonica So VLAs, would they entail any problem, or do they simply contradict the first property of SWAP()? \$\endgroup\$
    – Harith
    Commented Jun 7 at 19:25
  • 1
    \$\begingroup\$ Have not analyzed the VLA case, I simply see it as not following your assertion. \$\endgroup\$ Commented Jun 7 at 19:27
  • \$\begingroup\$ @chux-ReinstateMonica Fails to compile with "error: compound literal has variable size." and "error: expression in static assertion is not constant". Well well well, no one mentioned that anywhere. I would search around and edit the post. \$\endgroup\$
    – Harith
    Commented Jun 7 at 19:45
  • \$\begingroup\$ Why use three memcpy() calls rather than implementing memswap()? \$\endgroup\$
    – c19
    Commented Jun 8 at 23:49

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.