7
\$\begingroup\$

C23 has introduced attribute specifier sequences. Consequently, the header below attempts to conditionally define macros for these sequences (for my own use cases). In cases where a compiler does not yet support them but does support GNU C's attributes, the macros will expand to utilize GNU C's attributes, provided equivalent attributes exist. Otherwise, they will expand to nothing.

It also provides the definitions for nullptr and nullptr_t for versions prior to C2X/C23, and some other GNU C attributes.

#ifndef ATTRIB_H
#define ATTRIB_H 1

/* C2x/C23 or later? */
#if defined(__STDC__) && defined(__STDC_VERSION__) &&    \
    (__STDC_VERSION__ >= 202000L)                        \
    #include <stddef.h>     /* nullptr_t */
#else
    #define nullptr (void *)0
    typedef void * nullptr_t;
#endif                  /* nullptr/nullptr_t */

/*
 * Some notes:
 * 1) All attributes that take an optional or variable number of arguments will
 *    have two versions; one of the form `attr`, and one of the form `attrex`. 
 *    The latter shall expect one or more arguments.
 * 2) The attributes that have no equivalent in GNU C shall be replaced with 
 *    nothing except for `__maybe_unused__`, which would expand to `unused`.
 *    GNU C doesn't have `__reproducible__` or `__unsequenced__`, yet one may 
 *    be interested in looking at `pure` - which is more relaxed than 
 *    `__reproducible__` - and `const` - which is more strict than
 *    `__unsequenced__`.
 *
 * References:
 *  https://en.cppreference.com/w/c/language/attributes
 *  https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
 */

#if defined(__has_c_attribute)
    /* Every standard attribute whose name is of form `attr` can be also 
     * spelled as __attr__ and its meaning is not changed. All attributes
     * are specified with leading pairs of underscores, to avoid that they
     * macro-expand in case that the user had a macro with the same name.
     */
    #if __has_c_attribute(__maybe_unused__)
        #define ATTRIB_MAYBE_UNUSED             [[__maybe_unused__]]
    #endif                  /* __maybe_unused__ */

    #if __has_c_attribute(__deprecated__)
        /* Since __deprecated__ and __no_discard__ takes an optional parameter,
         * we define two versions of it.
         */
        #define ATTRIB_DEPRECATED               [[__deprecated__]]
        #define ATTRIB_DEPRECATEDEX(reason)     [[__deprecated__(#reason]]
    #endif                  /* __deprecated__ */

    #if __has_c_attribute(__fallthrough__)
        #define ATTRIB_FALLTHROUGH              [[__fallthrough__]]
    #endif                  /* __fallthrough__ */
     
    #if __has_c_attribute(__nodiscard__)
        #define ATTRIB_NODISCARD                [[__nodiscard__]]
        #define ATTRIB_NODISCARDEX(reason)      [[__nodiscard__(#reason)]
    #endif                  /* __nodiscard__ */

    #if __has_c_attribute(__unsequenced__)
        #define ATTRIB_UNSEQUENCED              [[__unsequenced__]]
    #endif                  /* __unsequenced__*/

    #if __has_c_attribute(__reproducible__)
        #define ATTRIB_REPRODUCIBLE             [[__reproducible__]]
    #endif                  /* __reproducible__*/
#elif defined(__GNUC__) || defined(__clang__)
    /* GNU C doesn't have a __maybe_unused__ attribute, but it has unused. */
    #define ATTRIB_MAYBE_UNUSED                 __attribute__((unused))
    #define ATTRIB_DEPRECATED                   __attribute__((deprecated))
    #define ATTRIB_DEPRECATEDEX(reason)         __attribute__(deprecated(#reason))
    #define ATTRIB_FALLTHROUGH                  __attribute__((fallthrough))
    
    /* GNU C doesn't have a __nodiscard__ attribute, but has warn_unused_result, 
     * but that doesn't take an optional argument, and raises a diagnostic message 
     * even after being casted to void, so we do not define it.
     * GCC doesn't have __reproducible__ and __unsequenced__.
     */
    #define ATTRIB_NODISCARD                    /* If only. */
    #define ATTRIB_NODISCARD(stream)            /* If only. */
    #define ATTRIB_UNSEQUENCED                  /* If only. */ 
    #define ATTRIB_REPRODUCIBLE                 /* If only. */ 
#else
    #define ATTRIB_MAYBE_UNUSED                 /* If only. */
    #define ATTRIB_DEPRECATED                   /* If only. */
    #define ATTRIB_DEPRECATEDEX(reason)         /* If only. */
    #define ATTRIB_FALLTHROUGH                  /* If only. */
    #define ATTRIB_NODISCARD                    /* If only. */
    #define ATTRIB_NODISCARD(stream)            /* If only. */
    #define ATTRIB_UNSEQUENCED                  /* If only. */ 
    #define ATTRIB_REPRODUCIBLE                 /* If only. */ 
#endif                  /* __has_c_attribute || __GNUC__ || __clang__ */


#if defined(__GNUC__) || defined(__clang__)
    /* To indicate that an allocation function both satisfies the nonaliasing 
     * property and has a deallocator associated with it, both the plain form
     * ot the attribute and the one with the deallocator must be used.
     *
     * Note that this attribute is not accepted on inline functions.
     */
    #define ATTRIB_MALLOC                       __attribute__(malloc)
    #define ATTRIB_MALLOCEX(...)                __attribute__(malloc, malloc (__VA_ARGS__))
    #define ATTRIB_PRINTF_LIKE(n, m)            __attribute__((format(printf, n, m)))
    #define ATTRIB_FORMAT(func, n, m)           __attribute__((format(func, n, m)))
    #define ATTRIB_COLD                         __attribute__((cold))
    #define ATTRIB_CONST                        __attribute__((const))
    #define ATTRIB_HOT                          __attribute__((hot))
    #define ATTRIB_PURE                         __attribute__((pure))
    #define ATTRIB_NONNULL                      __attribute__((nonnull))
    #define ATTRIB_NONNULLEX(...)               __attribute__((nonnull(__VA_ARGS__)
    #define ATTRIB_NULL_TERMINATED              __attribute__((null_terminated_string_arg))
    #define ATTRIB_NULL_TERMINATEDEX(...)       __attribute__((null_terminated_string_arg(__VA_ARGS__)))
    #define ATTRIB_UNAVAILABLE                  __attribute__((unavailable)) 
    #define ATTRIB_UNAVAILABLEEX(reason)        __attribute__((unavailable(#reason))) 
    #define ATTRIB_UNUSED                       __attribute__((unusued))
    #define ATTRIB_USED                         __attribute__((used))
    #define ATTRIB_WARN_UNUSED_RESULT           __attribute__((warn_unused_result))
    #define ATTRIB_RETURNS_NONNULL              __attribute__((returns_nonnull))
    #define ATTRIB_ALWAYS_INLINE                static inline __attribute__((always_inline))
    #define ATTRIB_NOINLINE                     __attribute__((noinline))
#else
    #define ATTRIB_MALLOC                       /* If only. */
    #define ATTRIB_MALLOCEX(...)                /* If only. */
    #define ATTRIB_PRINTF_LIKE(n, m)            /* If only. */
    #define ATTRIB_FORMAT(func, n, m)           /* If only. */
    #define ATTRIB_COLD                         /* If only. */
    #define ATTRIB_CONST                        /* If only. */
    #define ATTRIB_HOT                          /* If only. */
    #define ATTRIB_PURE                         /* If only. */
    #define ATTRIB_NONNULL                      /* If only. */
    #define ATTRIB_NONNULLEX(...)               /* If only. */
    #define ATTRIB_NULL_TERMINATED              /* If only. */
    #define ATTRIB_NULL_TERMINATEDEX(...)       /* If only. */
    #define ATTRIB_UNAVAILABLE                  /* If only. */
    #define ATTRIB_UNAVAILABLEEX(reason)        /* If only. */
    #define ATTRIB_UNUSED                       /* If only. */
    #define ATTRIB_USED                         /* If only. */
    #define ATTRIB_WARN_UNUSED_RESULT           /* If only. */
    #define ATTRIB_RETURNS_NONNULL              /* If only. */
    #define ATTRIB_ALWAYS_INLINE                /* If only. */
    #define ATTRIB_NOINLINE                     /* If only. */
#endif                  /* __GNUC__ || __clang__ */

#if defined(__GNUC__) || defined(__clang__)
    #define likely(x)       __builtin_expect((!!x), 1)
    #define unlikely(x)     __builtin_expect((!!x), 0)
#else
    #define likely(x)       (!!x)
    #define unlikely(x)     (!!x)
#endif                  /* __GNUC__ || __clang__ */

#endif                  /* ATTRIB_H */

Review Request:

How well does attrib.h accomplish the goal of using C23's and GNU specific attributes portably?

\$\endgroup\$

2 Answers 2

6
\$\begingroup\$

For any attribute that has either a standard version or a GCC/Clang extension, the separate #if clauses need to be #if/#elif blocks, This lets the header work either on any C23 compiler that supports the extension, or in GCC and Clang even if they do not have C23 enabled. These should also be a default #else clause that defines the macro as /**/ (which expands to a single whitespace token), so that the code will still compile portably on all compilers.

Intel’s ICX/ICPX is another compiler I often use that supports GCC and Clang attributes. You might also want to add support for MSVC __declspec.

You probably also want to support your header being included in a C++ program, with __cpp_attributes and __has_cpp_attribute.

A similar macro I frequently use to support tail recursion is:

#if __clang__ || __INTEL_LLVM_COMPILER 
#  define MUSTTAIL __attribute__((musttail))
#else
#  define MUSTTAIL /**/
#endif

Which lets me write MUSTTAIL return foo(bar); on all compilers, and have it compile as a no-op on those that do not support it. An updated version that works on GCC, Clang, ICX and MSVC:

#if defined(__has_c_attribute)
#  if __has_c_attribute(clang::musttail)
#    define MUSTTAIL [[clang::musttail]]
#  endif
#elif defined(__has_cpp_attribute)
#  if __has_cpp_attribute(clang::musttail)
#    define MUSTTAIL [[clang::musttail]]
#  endif
#endif

#if !defined(MUSTTAIL) && (__clang__ || __INTEL_LLVM_COMPILER)
#  define MUSTTAIL __attribute__((musttail))
#endif

#if !defined(MUSTTAIL)
#  define MUSTTAIL /**/
#endif

Which would be much nicer if compilers other than GCC supported #if defined(__has_c_attribute) && __has_c_attribute(foo)

What you currently have leaves attributes undefined if an implementation defines __has_c_attribute, but not that attribute. This is unlikely, since you only use it for standard attributes, but that will not hold as more are added.

Any compiler that supports this probably also supports the GCC extension __has__attribute, which works with either C or C++.

\$\endgroup\$
4
  • \$\begingroup\$ I ended up with this: github.com/Melkor-1/utils/blob/main/attrib.h \$\endgroup\$
    – Harith
    Mar 8 at 9:22
  • \$\begingroup\$ @Harith I’d just define nullptr to NULL, which is a special keyword on some compilers. If you’re going to use a portable definition, parenthesize it, as ((void*)0). Otherwise, this is a simple string substitution that could run afoul of precedence rules. \$\endgroup\$
    – Davislor
    Mar 8 at 13:25
  • \$\begingroup\$ @Harith The given definition of ATTRIB_MUSTTAIL also works only on C23 or C++11 and up, but doesn’t check for them. All compilers that support [[clang::musttail]] also support __attribute__((musttail)). I wrote my version to work on some other compiler that supports [[clang::musttail]], but you’d want to check __has_c_attribute or __has_cpp_attribute for this. \$\endgroup\$
    – Davislor
    Mar 8 at 13:26
  • \$\begingroup\$ My reason for defining it to NULL was this: en.cppreference.com/w/c/types/nullptr_t says that "the object representation of nullptr is same as that of (void*)0. If a program produces a nullptr_t value with a different object representation, the behavior is undefined." \$\endgroup\$
    – Harith
    Mar 9 at 16:22
2
\$\begingroup\$

This test is clearly over-doing it:

#if defined(__STDC__) && defined(__STDC_VERSION__) &&    \
    (__STDC_VERSION__ >= 202000L)                        \
    #include <stddef.h>     /* nullptr_t */

There's no need to test __STDC__, nor whether __STDC_VERSION__ is defined - an undefined value expands to zero when used in a preprocessor condition.

So we can simply use

#if __STDC_VERSION__ >= 202000L                        \
    #include <stddef.h>     /* nullptr_t */
\$\endgroup\$
1
  • 2
    \$\begingroup\$ defined(__STDC_VERSION__) is necessary to make the header compile cleanly with -Wundef, but checking __STDC__ as well is overkill. \$\endgroup\$
    – zwol
    Mar 3 at 16:02

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.