Explanation of C++ FAQ's unsafe macro?

后端 未结 4 362
既然无缘
既然无缘 2020-12-10 10:40

According to the C++ FAQ, macros are evil:

[9.5] Why should I use inline functions instead of plain old #define macros?

Because #d

4条回答
  •  醉梦人生
    2020-12-10 11:04

    unsafe(x) evaluates the expression x twice. Once to determine its truth value, and then a second time in one of the two branches of the ternary operator. The inline function safe receives an evaluated argument: the expression is evaluated once prior to the function call, and the function call works with local variables.

    unsafe is actually not quite as unsafe as it could be. The ternary operator introduces a sequence point between evaluating the test, and evaluating either the consequent or alternative expression. unsafe(x++) will reliably increments x twice, though, of course, the problem is that this behavior is unexpected. In general, macros which expand an expression more than once do not have this assurance. Usually, they produce outright undefined behavior!

    Circa 1999 I produced a library module module for catching uses of macros with side effects.

    So, you can write "evil" macros and use them, and the machine will catch situations where they are accidentally used with arguments that have side effects (provided you have adequate code coverage to hit those uses at run-time).

    Here is the test program, unsafe.c. Note that it includes a header file sfx.h and uses a SFX_CHECK macro in the expansion token sequence of unsafe:

    #include "sfx.h"
    
    #define unsafe(i)  \
              ( (SFX_CHECK(i)) >= 0 ? (i) : -(i) )
    
    inline
    int safe(int i)
    {
      return i >= 0 ? i : -i;
    }
    
    int f(void)
    {
      return 0;
    }
    
    int main(void)
    {
      int ans;
      int x = 0;
    
      ans = unsafe(x++);   // Error! x is incremented twice
      ans = unsafe(f());   // Danger! f() is called twice
    
      ans = safe(x++);     // Correct! x is incremented once
      ans = safe(f());     // Correct! f() is called once
    }
    

    We compile everything and run from a Linux shell prompt:

    $ gcc unsafe.c hash.c except.c sfx.c -o unsafe
    $ ./unsafe
    unsafe.c:22: expression "x++" has side effects
    unsafe.c:23: expression "f()" may have side effects
    

    Note that x++ certainly has side effects, whereas f may or may not. So the messages are differently worded. A function being called twice is not necessarily an issue, because a function might be pure (have no side effects).

    You can get that here if you're curious about how it works. There is a run-time penalty if the debugging is enabled; of course SFX_CHECK can be disabled so it does nothing (similarly to assert).

    The first time an SFX_CHECK protected expression is evaluated, it is parsed in order to determine whether it might have side effects. Because this parsing is done without any access to symbol table information (how identifiers are declared), it is ambiguous. The parser pursues multiple parsing strategies. Backtracking is done using an exception-handling library based on setjmp/longjmp. The parse results are stored in a hash table keyed on the textual expression for faster retrieval on future evaluations.

提交回复
热议问题