4

I have a simple code in header.h-

#define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;} 

This header.h is included in a code.c file, but my requirement is- I want SWAP to be checked first like-

#ifndef SWAP(a, b)
#define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;}
#endif

Is this correct or I don't have to provide the argument to the first line?

A. Gupta
  • 77
  • 2
  • 9
  • 6
    Just use `::std::swap` instead. – user7860670 Jan 17 '18 at 05:47
  • 9
    `SWAP(a, a)` fails with this definition. – jxh Jan 17 '18 at 05:49
  • 3
    If you're saying this is to be used in a C file, why tag your question as C++? –  Jan 17 '18 at 06:00
  • 2
    @hvd: It is not uncommon to have a header file that gets `#include`d by both C source files and C++ source files. – jxh Jan 17 '18 at 06:46
  • @jxh Sure, and that's why I didn't remove the tags yet. However, there's nothing in the question to indicate that's the case here, so either the tags are inappropriate and should be removed, or the OP should edit the question to make it clear why the tags are appropriate. What seems most likely to me is that the OP just put them in without thinking about it and now ended up with a lot of useless extra information in the answers. –  Jan 17 '18 at 06:49
  • I can agree with jxh; I've seen a few times here and there a `declaration or interface file` - header with its declarations and pre processor directives for use with either C or C++ then there were accompanying `*.c` & `*.cpp` files with the actual definitions for each language. It was a library base that supported both C, C++ & other languages such as C#, Java & F#. – Francis Cugler Jan 17 '18 at 06:49
  • 1
    1) Never use XOR swaps, they are dangerous, unreadable and most likely not faster. 2) Use a function instead of a function-like macro. 3) When writing function-like macros (which you should avoid), protect all macro parameters against accidental precedence issues by surrounding them with parenthesis, as demonstrated in your beginner-level C programming book. – Lundin Jan 17 '18 at 09:00
  • Expanding on Lundin's 1): a) if XOR swaps were still so cool, the compiler would know about them; Back then, an average coder could write assembler that was faster than compiler-generated. Now, it takes an expert to beat it, if even possible. b) as they were invented, registers were scarce (6 usable GP on x86, maybe 7 if you messed with the frame pointer) and caches were absent or sucked. Now, there are many more (x86-64 has at least 14 GP – also reg renaming) and very effective caching. c) they are RMW ops and are most likely more expensive than assignment (`mov`) after translation to µcode. – Arne Vogel Jan 17 '18 at 11:55
  • 1
    You might want to check the answer here https://stackoverflow.com/a/1826175/597607 for *how* much slower the xor-trick actually is. – Bo Persson Jan 17 '18 at 12:17

6 Answers6

7

You want to code

#ifndef SWAP
#define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;}
#endif

The #ifndef is just looking into the preprocessor's symbol table (for the presence or absence of some given preprocessor symbol). It does not care about the arguments of your SWAP macro.

Of course in genuine C++11 you should prefer the standard std::swap function. It is typesafe, more readable, and safer (think about SWAP(t[i++],i) as a misbehaving example).

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 5
    To be fair, `std::swap(t[i++],i)` is undefined behaviour (assuming `i` is `int`) until C++17, where there are still multiple possible well-defined results. Your point still stands, of course. `SWAP(t[i], i)` wouldn't go very well. – chris Jan 17 '18 at 06:01
  • @chris Good point, `swap(t[i++], t[last])` would be a better example (or just any second argument not related to `i`). – Arne Vogel Jan 17 '18 at 11:44
4

It is incorrect.

The correct usage of #ifndef is:

#ifndef SWAP
#define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;}
#endif

From http://en.cppreference.com/w/cpp/preprocessor/conditional:

#ifndef identifier

R Sahu
  • 204,454
  • 14
  • 159
  • 270
4

Before asking the question you could have looked into the standard to get an idea of how it is used.

From standard - §6.10.1p5

Preprocessing directives of the forms

# ifdef identifier new-line groupopt
# ifndef identifier new-line groupopt

check whether the identifier is or is not currently defined as a macro name. Their conditions are equivalent to #if defined identifier and #if !defined identifier respectively.

Then again if you are not sure what is the macro name and what are the parameters etc..

From standard §6.10.3.p10

A preprocessing directive of the form

# define identifier lparen identifier-listopt ) replacement-list new-line
# define identifier lparen ... ) replacement-list new-line
# define identifier lparen identifier-list , ... ) replacement-list new-line

defines a function-like macro with parameters, whose use is similar syntactically to a function call. The parameters are specified by the optional list of identifiers, whose scope extends from their declaration in the identifier list until the new-line character that terminates the #define preprocessing directive. Each subsequent instance of the function-like macro name followed by a ( as the next preprocessing token introduces the sequence of preprocessing tokens that is replaced by the replacement list in the definition (an invocation of the macro)....

The last section just will let you know enough what should be written in the ifndef in place of identifier. It is clear from the highlighted parts.

In C you have to a considerable amount of work to write a generic and correct swap macro. For two same value your swap will not work as you expect it to.

user2736738
  • 30,591
  • 5
  • 42
  • 56
4

To briefly answer your question, it is as the other answers have already stated. The #ifdef/#ifndef conditional only cares about the macro identifier, so the arguments are not part of its syntax.

However, your macro has a couple of weaknesses that should be addressed. First, note that using the XOR-operator for swapping, although a commonly taught trick to avoid using a temporary, fails if the two arguments are the same object. This is because the result of the first XOR would be 0, and the remaining XOR steps cannot recover the original value. Second, this version of the macro fails for pointer types, because XOR wants integral types. Third, the macro invokes the arguments multiple times, which will cause problems if the argument is an expression with side effects. Fourth, compound statements in a macro should be guarded by do .. while (0) to allow the macro to expand into a statement. This makes the macro syntactically cleaner, so that a semi-colon after it is not spurious.

As explained in a separate answer, in C++, use std::swap instead of defining your own. Unfortunately, C does not provide a generic swapping utility function. However, it is not difficult to author a generic function:

static inline void swap_generic (void *a, void *b, void *t, size_t sz) {
    if (a != b) {
        memcpy(t, a, sz);
        memcpy(a, b, sz);
        memcpy(b, t, sz);
    }
}

Your macro could then invoke this function.

#ifndef SWAP
# ifdef __cplusplus
#  define SWAP(a, b) std::swap(a, b)
# else
#  define SWAP_ASSERT(X) _Static_assert(X, #X)
#  if  __STDC_VERSION__ < 201112L
#   undef SWAP_ASSERT
#   define SWAP_ASSERT(X) struct dummy
#  endif
#  define SWAP(a, b) do {                                \
       SWAP_ASSERT(sizeof(a) == sizeof(b));              \
       char t[sizeof(a) != sizeof(b) ? -1 : sizeof(a)];  \
       swap_generic(&(a), &(b), t, sizeof(a));           \
   } while (0)
# endif
#endif

Note how we use std::swap if C++ is detected.

If you use a C compiler that supports the typeof extension, then the macro can be simplified greatly, since you do not need a generic swapping function.

#ifndef SWAP
# ifdef __cplusplus
#  define SWAP(a, b) std::swap(a, b)
# else
#  define SWAP(a, b) do {                      \
       typeof(a) *p = &(a), *q = &(b), t = *p; \
       *p = *q;                                \
       *q = t;                                 \
   } while (0)
# endif
#endif

Note that the typeof version promotes better type safety.

jxh
  • 69,070
  • 8
  • 110
  • 193
  • Wow I really like your macro design! Looks very handy; yet why go through the trouble? Couldn't you achieve the same with a simple in place lambda? -rhetorical question- ... – Francis Cugler Jan 17 '18 at 06:35
  • I know that C lacks lambdas... It's been a really long time since I've written anything in C probably 20 years... I can not remember but I thought C was "lacking" in macros too,... I know C doesn't have templates, nor classes only structs... and some of the same key words mean one thing in C but something different in C++. It might not be the macros, it could be namespaces that I was thinking of... – Francis Cugler Jan 17 '18 at 06:41
  • @FrancisCugler: I am not clear what your point is, but the complexity in the macro I propose is there to resolve the 4 issues raised in my answer when the macro is used in C source, and use `std:swap` instead when C++ is detected. – jxh Jan 17 '18 at 06:49
  • My apology; I'm starting to lose train of thought as it's starting to get late where I'll be finishing up here within the next hour. My grammer - typing is bad right now so-to-speak - tongue-in-cheek... – Francis Cugler Jan 17 '18 at 06:52
  • My original statement was based on the fact that I don't typically write in C and that I mostly use C++... – Francis Cugler Jan 17 '18 at 06:54
  • I did learn something new about Macros that I either didn't know or long forgot as I do not typically use them, but that is about containing compound statements in a `do { ... } while(0)` loop. – Francis Cugler Jan 17 '18 at 07:05
  • The C macro is quite ugly - an inline function should be used instead. Furthermore, C supports static assert, no need for ugly -1 array size tricks. Furthermore, the do while 0 trick is harmful as it allows coding styles that do not always use compound statements to exist. (That is, "Please Mr. compiler, let me write the Apple goto fail bug, the most expensive bug in SW history".) Overall, use modern C programming instead of old tricks from the early 90s. – Lundin Jan 17 '18 at 09:05
  • @Lundin: I don't think a `static_assert` actually increases the clarity of the code here, just the clarity of the compile time error. I do call an `inline` function, but the size information must still be passed in, or the macro signature has to change to include the type. The primitive types can be inferred from `_Generic`, but that would still require writing a macro. – jxh Jan 17 '18 at 09:14
  • @jxh A _Generic would enforce you to implement a "functor" just as C++ would enforce you to implement some operator overloading, so I don't think that's ideal. Rather, the programmer should make a type-specific function such as `int_swap` and it should be inline. – Lundin Jan 17 '18 at 09:22
  • (As a parenthesis, any macro would btw be problematic if both parameters point at the same address. In C this could be solved with `restrict` pointers, or alternatively by using memmove instead of memcpy.) – Lundin Jan 17 '18 at 09:23
  • @Lundin: You make valid points, but just to give the alternate points of view: C macros are merely a tool in the C programmer's toolbox, and any tool can be abused. The coding style objection is curious, since `do`...`while(0)` just makes a macro behave syntactically like a `void` function call. Implementing type-specific functions fits `_Generic` usage, as `_Generic` allows you to dispatch to the right type-specific function. – jxh Jan 17 '18 at 17:14
  • @jxh The single purpose of using `do{ }while(0)` in a macro, rather than just `{ }`, is to bad allow coding styles like `if(x) MACRO(); else ...` to exist. It has no other purpose. Which is why `{ }` is preferable, to discourage a coding style that ultimately leads to Apple goto fail bugs. – Lundin Jan 18 '18 at 07:29
  • @Lundin: `do`...`while(0)` did not cause the goto fail bug. Buggy code did that. As I said, any tool can be abused. You shouldn't use syntax as an excuse for writing buggy code. If a coding style is important, make sure the code reviewers enforce it. – jxh Jan 18 '18 at 09:53
  • There is no doubt that the "Apple goto fail" was caused by a bad coding style. At the point where a certain coding style is blatantly dangerous, it is not a subjective matter of preference any longer. It is clear that a coding style without compound statements is dangerous and should not be used. This is why all well-known coding standards such as MISRA and CERT ban that style. And as I already explained, the whole purpose of the somewhat obscure `do { } while(0)` trick, is to propagate for this bad and dangerous coding style. If you aren't using bad and dangerous style, you would use `{ }`. – Lundin Jan 18 '18 at 10:09
  • @Lundin: It doesn't prevent someone from leaving off the `;`. – jxh Jan 18 '18 at 10:13
  • @Lundin: My point being, code reviewers (even the robotic kind) have to enforce good style. C syntax is just syntax. – jxh Jan 18 '18 at 10:15
0
#ifndef SWAP
#define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;}
#endif

above change will work for c,c++11. probably it will work for all the versions of c++XX.

Bharath T S
  • 787
  • 6
  • 6
  • Yeah it would work but it's not particularly safe... what if `(a==b)`? what if `(a &| b)` are pointers or expressions... etc. – Francis Cugler Jan 17 '18 at 06:31
0

In the older days of C++ before safer smart pointers std::shared_ptr<T> & std::unique_ptr<T> came around when using new & delete & new [] & delete [] these are some older macros that I once used in C++ for helping to manage memory.

#ifndef SAFE_DELETE
    #define SAFE_DELETE(p)  { if(p) { delete (p); (p) = nullptr; } }
#endif

#ifndef SAFE_DELETE_ARRAY
    #define SAFE_DELETE_ARRAY(p)    { if(p) { delete [] (p); (p) = nullptr; } }
#endif 

Similar to what you are trying to achieve: the design of these types of macros typically look to see if the tag - identifier is not yet defined. If it isn't then you define the name along with its parameter and functionality, then you end the if definition. Also as a side note when working with macros it may seem like over doing it but it is always advisable to enclose each parameter name in parenthesis. So in your case you would want something like:

#ifndef SWAP
    #define SWAP(a, b)  if ( (a) != (b) ) { \
                            ((a) ^= (b));   \
                            ((b) ^= (a));   \
                            ((a) ^= (b));   \
                        } else {            \ 
                            ((a) = (a));    \
                            ((b) = (b));    \
                        }                   
#endif

Now the above macro is simplistic and it doesn't prevent from invalid types such as pointers, expressions, references etc., but it should work with any built in default basic types.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59