Using array_length macro outside function

Message ID 87a7jbpqqm.fsf@oldenburg2.str.redhat.com
State Superseded
Headers

Commit Message

Florian Weimer Feb. 4, 2019, 2:34 p.m. UTC
  * TAMUKI Shoichi:

> [1] to calculate the number of elements in the array.
>
> [1] https://sourceware.org/ml/libc-alpha/2017-10/msg01054.html
>
> Now, I tried to further improve this as patched below;
>
> ----------------------------------------------------------------------
> diff --git a/time/tst-strftime2.c b/time/tst-strftime2.c
> index 57d2144..6c2d359 100644
> --- a/time/tst-strftime2.c
> +++ b/time/tst-strftime2.c
> @@ -25,8 +25,10 @@
>  #include <string.h>
>  
>  static const char *locales[] = { "ja_JP.UTF-8", "lo_LA.UTF-8", "th_TH.UTF-8" };
> +#define nlocales array_length (locales)

> +static char ref[nlocales][nformats][ndates][100];

The other problem is that array_length (locales) is not a constant
expression because a statement expression is never a constant
expression.  I had not considered these problems when I wrote the
array_length macro.

I would like to retain the verification that the macro argument is not a
pointer.  What do you think about the patch below?

Thanks,
Florian

array_length: Make usable as a constant expression

Do not use a statement expression and _Static_assert in array_length,
so that array_length can be used at file scope and as a constant
expression.

The compiler error message looks like this, so this is still
acceptable:

t.c:3:28: error: zero width for bit-field ‘ARGUMENT_NOT_AN_ARRAY’
   0 * sizeof (struct { int ARGUMENT_NOT_AN_ARRAY: \
                            ^~~~~~~~~~~~~~~~~~~~~
t.c:10:7: note: in expansion of macro ‘array_length’
 int b[array_length (c) + 1];
       ^~~~~~~~~~~~

2019-02-04  Florian Weimer  <fweimer@redhat.com>

	* include/array_length.h (array_length): Do not use a statement
	expression and _Static_assert, so that array_length can be used at
	file scope and as a constant expression.
  

Comments

Joseph Myers Feb. 4, 2019, 11:33 p.m. UTC | #1
On Mon, 4 Feb 2019, Florian Weimer wrote:

> Do not use a statement expression and _Static_assert in array_length,
> so that array_length can be used at file scope and as a constant
> expression.
> 
> The compiler error message looks like this, so this is still
> acceptable:
> 
> t.c:3:28: error: zero width for bit-field ‘ARGUMENT_NOT_AN_ARRAY’
>    0 * sizeof (struct { int ARGUMENT_NOT_AN_ARRAY: \

You can use _Static_assert inside a struct; you don't need to do things 
with bit-field sizes.
  
Paul Eggert Feb. 5, 2019, 12:32 a.m. UTC | #2
On 2/4/19 6:34 AM, Florian Weimer wrote:

 > +#define array_length(var)                                       \
 > +  (sizeof (var) / sizeof ((var)[0])                             \
 > +   + 0 * sizeof (struct { int ARGUMENT_NOT_AN_ARRAY:            \
 > +       1 - (__builtin_types_compatible_p                        \
 > +            (__typeof (var), __typeof (&(var)[0]))); }))

In Gnulib apps we've needed static assertions in expressions so often 
that we have a macro 'verify_expr (R, E)' that means "verify R 
statically, and yield the value of E". With this macro, you could 
rewrite the above definition as:

   #define array_length(var)                                             \
     verify_expr (!__builtin_types_compatible_p (__typeof (var),         \
                                                 __typeof (&(var)[0])),  \
                  sizeof (var) / sizeof ((var)[0]))

which is quite a bit easier to follow. So I suggest appending something 
like the following to include/verify.h:

   #define verify_expr(r, e) \
     (sizeof (struct { _Static_assert (r, #r); char __dummy; }) \
      ? (e) : (e))

Then we can have array_length.h include verify.h and use the 
abovementioned simpler array_length.

(The Gnulib verify_expr caters to non-GNU and pre-C99 compilers and so 
is more complicated, but we don't need that complexity in glibc.)
  
TAMUKI Shoichi Feb. 5, 2019, 6:17 a.m. UTC | #3
Hello Florian-san,

From: Florian Weimer <fweimer@redhat.com>
Subject: Re: Using array_length macro outside function
Date: Mon, 04 Feb 2019 15:34:09 +0100

> The other problem is that array_length (locales) is not a constant
> expression because a statement expression is never a constant
> expression.  I had not considered these problems when I wrote the
> array_length macro.
> 
> I would like to retain the verification that the macro argument is not a
> pointer.  What do you think about the patch below?

Thank you.  Applying your patch, I have confirmed that the test can be
compiled successfully.

Regards,
TAMUKI Shoichi
  

Patch

diff --git a/include/array_length.h b/include/array_length.h
index 65f583063d..cee8897ba7 100644
--- a/include/array_length.h
+++ b/include/array_length.h
@@ -21,13 +21,11 @@ 
 
 /* array_length (VAR) is the number of elements in the array VAR.  VAR
    must evaluate to an array, not a pointer.  */
-#define array_length(var)                                               \
-  __extension__ ({                                                      \
-    _Static_assert (!__builtin_types_compatible_p                       \
-                    (__typeof (var), __typeof (&(var)[0])),             \
-                    "argument must be an array");                       \
-    sizeof (var) / sizeof ((var)[0]);                                   \
-  })
+#define array_length(var)                                       \
+  (sizeof (var) / sizeof ((var)[0])                             \
+   + 0 * sizeof (struct { int ARGUMENT_NOT_AN_ARRAY:            \
+       1 - (__builtin_types_compatible_p                        \
+            (__typeof (var), __typeof (&(var)[0]))); }))
 
 /* array_end (VAR) is a pointer one past the end of the array VAR.
    VAR must evaluate to an array, not a pointer.  */