2

Following snippet is from mac sdk signal.h:

#define sigemptyset(set)    (*(set) = 0, 0)

just wonder what does , 0) do?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
mzoz
  • 1,273
  • 1
  • 14
  • 28
  • I don't think it's a conforming implementation. POSIX says it's supposed to be a function with the prototype `int sigemptyset(sigset_t *set);`. With the macro approach, they should typecheck the argument too and provide a function implementation as well. – Petr Skocik Feb 13 '23 at 15:11
  • 1
    @PSkocik: the function prototype is present in the header file and implemented in the library, the macro allows for inline expansion, sacrificing type checking to shave a few cycles. – chqrlie Feb 13 '23 at 15:18
  • 1
    @chqrlie Typechecking with `*(sigset_t*){set}` would be trivial for newer C's, which have compound literals. Could be done with ancient C features too: `*(0?(sigset_t*)0:(set))`. For codegen, the macro is sure better than a function call for such a trivial thing, but the unnecessary deviations from the standard annoy me a bit (which is ironic, because I don't even like the standards much). Anyway, nice answer. +1 – Petr Skocik Feb 13 '23 at 15:29
  • 1
    @PSkocik: interesting ways to perform type checking in macros without multi evaluation nor statement expression extensions! – chqrlie Feb 13 '23 at 15:38

2 Answers2

3

The macro expands to a comma expression: the left operand, evaluated first, sets the object pointed to by set to 0, then the right operand is evaluated and its value is the value of the whole expression, hence: 0.

In other words, the macro behaves like a function that always succeeds, success indicated by the return value 0.

Except for the type checking, the macro is equivalent to:

#include <signal.h>

int sigemptyset(sigset_t *set) {
    *set = 0;
    return 0;
}

Note that the <signal.h> header file also contains a prototype for this function:

int sigemptyset(sigset_t *);

In your code, a call sigemptyset(&sigset) invokes the macro, but you can force a reference to the library function by writing (sigemptyset)(&sigset) or by taking a pointer to the function. The macro allows for inline expansion without a change of prototype. clang can perform inline expansion at link time for small functions, but not for functions defined inside dynamic libraries.

The same trick is used for other functions in the header file, for which the , 0) is necessary for the expression to evaluate to 0 with type int:

#define sigaddset(set, signo)   (*(set) |= __sigbits(signo), 0)
#define sigdelset(set, signo)   (*(set) &= ~__sigbits(signo), 0)
#define sigemptyset(set)        (*(set) = 0, 0)
#define sigfillset(set)         (*(set) = ~(sigset_t)0, 0)
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 1
    I would add that the comma operator would not be necessary here because the left expression is 0, but the same pattern is probably used for other macros replacing related functions like `sigfillset` or `sigaddset` where the assignment expression is not 0. – Bodo Feb 13 '23 at 15:07
  • 1
    The same pattern is used indeed for other macros in `` and using `, 0)` makes the expression evaluate to `0` with type `int` without a cast. `sigset` is defined as a `__uint32_t`, defining the macro as `#define sigemptyset(set) (*(set) = 0)` would have type `unsigned` and defining it as `#define sigemptyset(set) ((int)(*(set) = 0))` is no better than using the comma operator. – chqrlie Feb 13 '23 at 15:09
1

In this expression

(*(set) = 0, 0)

there is used the comma operator.

The result of the expression is 0.

As a side effect the object pointed to by the pointer set is set to 0.

From the C Standard (6.5.17 Comma operator)

2 The left operand of a comma operator is evaluated as a void expression; there is a sequence point between its evaluation and that of the right operand. Then the right operand is evaluated; the result has its type and value.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335