https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121275

            Bug ID: 121275
           Summary: Extend _Countof() to work with array parameters
           Product: gcc
           Version: 16.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c
          Assignee: unassigned at gcc dot gnu.org
          Reporter: foss+...@alejandro-colomar.es
  Target Milestone: ---

Hi!

I'll work on this once Martin's patch lands on trunk.
<https://inbox.sourceware.org/gcc-patches/3d92a9ba50d4f04b561010fc6f35597babae05a6.ca...@tugraz.at/>

Below is a draft of a proposal we're discussing in the C Committee.


Have a lovely day!
Alex

---
Name
        alx-0054r2 - _Countof array parameters

Principles
        -  Uphold the character of the language.
        -  Keep the language small and simple.
        -  Avoid ambiguities.
        -  Pay attention to performance.
        -  Codify existing practice to address evident deficiencies.
        -  Do not leave features in an underdeveloped state.
        -  Avoid quiet changes.
        -  Enable secure programming.

        And from previous charters:

        C11:
        -  No invention, without exception.

        C23:
        -  APIs should be self-documenting when possible.

Category
        Do the Right Thing(tm).

Author
        Alejandro Colomar <a...@kernel.org>

        Cc: Martin Uecker <uec...@tugraz.at>
        Cc: Christopher Bazley <chris.bazley.w...@gmail.com>
        Cc: Kees Cook <keesc...@chromium.org>
        Cc: Linus Torvalds <torva...@linuxfoundation.org>
        Cc: Aaron Ballman <aa...@aaronballman.com>

History
        <https://www.alejandro-colomar.es/src/alx/alx/wg14/alx-0054.git/>

        r0 (2025-07-27):
        -  Initial draft.

        r1 (2025-07-28):
        -  tfix.
        -  Require that the array parameter is const-qualified.
        -  Add 'Future directions'.
        -  Mention 'Avoid quiet changes'.

        r2 (2025-07-28):
        -  tfix.

See also
        -  GCC diagnostic proposal:
           -Wsizeof-array
           <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121270>

        -  GCC dialect flag proposal:
           -fconst-array-parameters
           <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121271>

        This proposal depends on:

        -  n2906 (2022-01-04; Uecker, "Consistency of Parameters Declared as
Arrays (updates N2779)")
           <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2906.pdf>

        This proposal would benefit from:

        -  n3433 (2025-01-25; Bazley, "Alternative syntax for forward
declaration of parameters")
           <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3433.pdf>

        This proposal conflicts with:

        -  n3656 (2025-07-27; Na, "Dependent attributes")
           <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3656.pdf>

Rationale
        Historically, the C language had a way (the one and only way) to
        express that a parameter to a function is in reality an array.
        The ABI is that of a pointer, but usage is (almost) identical to
        an array (except for sizeof, and other operators that take into
        account the type of the operand)

                void foo(size_t n, int arr[n]);

        People have complained about them, mainly because they're
        dangerous.  People have tried to solve this with alternatives
        (fat pointers, std::array, std::vector, std::span, etc.) in C++.
        C has remained free of those things (for good) so far.  Now
        people try to add alternatives in C (n3656, Clang), disregarding
        all the principles mentioned above.

        A solution in C must integrate with the rest of the language,
        and that means using array syntax (as we've done forever in C),
        not changing the rules for parsing C code, not inventing
        completely new stuff at all.

        The solution has been there all the time; we only need to read
        all the information that is already present in most code: array
        parameter bounds.  Programmers are already conscious enough that
        they provide this information even if the compiler ignores it;
        it's good for their own readability (and some compilers already
        use it for diagnostics, including GCC).

        Now, we have _Countof().  This allows us to go farther than GCC.
        We can not only use that information for diagnostics, but we can
        use it for our actual code:

                void
                foo(size_t n, int a[const n])
                {
                        for (size_t i = 0; i < _Countof(a); i++)
                                a[i] = 0;
                }

        where _Countof(a) evaluates to 'n', the Right Thing.

        This would be directly usable in APIs where the size is visible
        before the array parameter, which is true in many internal APIs
        in projects.

        For the libc APIs, where this is not possible (due to the order
        of parameters), we'd need to standardize GCC's forward
        declaration of parameters, as proposed by n3433.

    const
        The array parameter must be const-qualified, to prevent it from
        being modified (advanced), which would turn the length
        information obsolete by the time _Countof() is used.

    Better than "dependent" attributes
        The approach with array notation and _Countof() not only enables
        some static analysis.  It allows writing better code.

        Let's say we want to call snprintf(3) safely.  I personally use
        the following wrappers (T for truncation):

                #define STPRINTF(s, fmt, ...)                         \
                (                                                     \
                        stprintf(s, countof(s), fmt __VA_OPT__(,) __VA_ARGS__)
\
                )


                [[gnu::format(printf, 3, 4)]]
                int
                stprintf(int size;
                    char s[restrict size], int size,
                    const char *restrict fmt, ...)
                {
                        int      len;
                        va_list  ap;

                        va_start(ap, fmt);
                        len = vstprintf(s, size, fmt, ap);
                        va_end(ap);

                        return len;
                }

                [[gnu::format(printf, 3, 0)]]
                int
                vstprintf(int size;
                    char s[restrict size], int size,
                    const char *restrict fmt, va_list ap)
                {
                        int  len;

                        len = vsnprintf(s, size, fmt, ap);
                        if (len == -1)
                                return -1;
                        if (len >= size) {
                                errno = E2BIG;
                                return -1;
                        }

                        return len;
                }

        These allow one to call STPRINTF() on arrays like this:

                char  buf[PATH_MAX];

                if (STPRINTF(buf, "/proc/%d/", pid) == -1) {
                        goto fail;

        Not needing to specify the array size at call site at all.
        By extending the _Countof operator to work on array parameters,
        we could do the same exact thing with them:

                int
                bar(int size, char buf[const 100], pid_t pid)
                {
                        if (STPRINTF(buf, "/proc/%d/", pid) == -1) {
                                return -1;

                        ...

                        return 0;
                }

        This is only possible with _Countof(), and only possible with
        array notation.  _Countof() will never support pointers; that's
        a promise, by design.  Thus, this will not work with the
        attributes proposed in n3656.

    Arbitrarily complex prototypes
        This feature can be used for more complex functions, such as
        [v]seprintf(), proposed in alx-0049.

                [[gnu::format(printf, 3, 0)]]
                char *
                vseprintf(char *const restrict p, const char *restrict end;
                    char p[const restrict p ? end - p : 0],
                    const char end[restrict 0],
                    const char *restrict format, va_list ap)
                {
                        int        len;
                        ptrdiff_t  size;

                        if (p == NULL)
                                return NULL;

                        len = vsnprintf(p, _Countof(p), fmt, ap);

                        if (len == -1)
                                return NULL;
                        if (len >= _Countof(p)) {
                                errno = E2BIG;
                                return NULL;
                        }

                        return p + len;
                }

        Such a simple feature (4 lines added to the standard), provides
        very strong safety improvements to the C language, ruling out
        entire classes of bugs from the language; not only by enabling
        static analysis, but even better: by not being able to write
        them in the first place, with wrapper macros that call
        _Countof() as appropriate.

Prior art
        This reuses old C syntax with the meaning it always had, even if
        the standard ignores such syntax.

        And for the _Countof() extension, Martin and I are working on
        implementing this in GCC.

Future directions
    -Wsizeof-array
        We could make it a constraint violation to use sizeof(array).
        Now that we have _Countof(), it's safer to use it even for the
        size in bytes of an array, as

                _Countof(array) * sizeof(array[0])

        As it prevents accidentally using pointers.  And it would even
        work with array parameters, unlike sizeof().

        It's a breaking change, but it's not a silent one, so we're
        covered by 'Avoid quiet changes'.

    -fconst-array-parameters
        In the future, we could make all array parameters
        const-qualified.  I'd suggest compilers to add a flag that turns
        the dialect into that, which eventually turn into the default.

        This would also be a breaking change, but again we're covered by
        'Avoid quiet changes'.

    _Generic(), typeof()
        The combination of the two items above will make array
        parameters quite close to actual arrays.  Actually, almost
        indistinguishable, if not by _Generic() in combination with
        typeof().  We may want to tweak that too afterwards.  We'll see.

Proposed wording
        Based on N3550.

    6.5.4.5  The sizeof, _Countof, and alignof operators
        @@ Constraints, p1
        ...
         The _Countof operator shall only be applied to
         an expression that has a complete array type,
        -or to the parenthesized name of such a type.
        +or to the parenthesized name of such a type,
        +or to a <b>const</b>-qualified array parameter of specified size.
        ...

        @@ Semantics, p5
         The _Countof operator yields
         the number of elements of its operand.
         The number of elements is determined from
         the type of the operand.
        +If the operand is an array parameter,
        +the number of elements is determined from
        +the array type used in its declaration.
         The result is an integer.
         If the number of elements of the array type is variable,
         the operand is evaluated;
         otherwise,
         the operand is not evaluated
         and the expression is an integer constant expression.

Reply via email to