Paul Eggert wrote:
> >> 1. Gnulib lib/string.in.h should contain a comment saying that its
> >> strnul is stricter with void pointers than glibc's strchr, and why.
> >
> > I would prefer to get this fixed in glibc.
>
> Regardless of whether it's fixed in glibc, we should document the
> discrepancy/bug/whatever, as the issue exists now with current glibc and
> current Gnulib.
Oh, I see now what you mean: In ISO C, any function call that takes
a 'char *' argument can also take a 'void *' argument. In his definition
of __glibc_const_generic, Joseph simulated this behaviour by explicitly
allowing 'void *' and 'const void *' arguments, in all const-generic
macros. I withdraw the claim it's a glibc bug; this choice makes perfect
sense (for backward compatibility with older glibc versions where strchr
was a plain function).
Reproduced with the attached foo.c.
2026-02-23 Bruno Haible <[email protected]>
strnul: Accept 'void *' and 'const void *' arguments in C mode.
Reported by Paul Eggert in
<https://lists.gnu.org/archive/html/bug-gnulib/2026-02/msg00170.html>.
* lib/string.in.h (strnul): Use a conditional expression in _Generic.
* tests/test-strnul.c (main): Add test cases with 'void *' argument.
diff --git a/lib/string.in.h b/lib/string.in.h
index 599203c44d..33422a06e7 100644
--- a/lib/string.in.h
+++ b/lib/string.in.h
@@ -1271,10 +1271,16 @@ template <> inline char *strnul< char *> (
char *s)
|| (defined __SUNPRO_C && __SUNPRO_C >= 0x5150) \
|| (__STDC_VERSION__ >= 201112L && !defined __GNUC__)
/* The compiler supports _Generic from ISO C11. */
+/* Since in C (but not in C++!), any function that accepts a '[const] char *'
+ also accepts a '[const] void *' as argument, we make sure that the function-
+ like macro does the same, by mapping its type first:
+ char *, void * -> void *
+ const char *, const void * -> const void *
+ This mapping is done through the conditional expression. */
# define strnul(s) \
- _Generic (s, \
- char * : (char *) gl_strnul (s), \
- const char * : gl_strnul (s))
+ _Generic (1 ? (s) : (void *) 99, \
+ void * : (char *) gl_strnul (s), \
+ const void * : gl_strnul (s))
# else
# define strnul(s) \
((char *) gl_strnul (s))
diff --git a/tests/test-strnul.c b/tests/test-strnul.c
index b9b4970ad6..7c068d8f1e 100644
--- a/tests/test-strnul.c
+++ b/tests/test-strnul.c
@@ -33,5 +33,13 @@ main ()
ASSERT (ro_nul - ro == 3);
ASSERT (rw_nul - rw == 3);
+#if !defined __cplusplus
+ const char *rov_nul = strnul ((const void *) ro);
+ char *rwv_nul = strnul ((void *) rw);
+
+ ASSERT (rov_nul - ro == 3);
+ ASSERT (rwv_nul - rw == 3);
+#endif
+
return test_exit_status;
}
#include <string.h>
#if 1
extern char *my_strchr (const char *, int);
#define strchr my_strchr
#endif
extern const char *gl_strnul (const char *string);
# define strnul(s) \
_Generic (1 ? (s) : (void *) (s), \
void * : (char *) gl_strnul (s), \
const void * : gl_strnul (s))
char str[] = "foo";
int main ()
{
const char *a = strchr ((const char *) str, 'o');
char *b = strchr ((char *) str, 'o');
const char *c = strchr ((const void *) str, 'o');
char *d = strchr ((void *) str, 'o');
const char *na = strnul ((const char *) str);
char *nb = strnul ((char *) str);
const char *nc = strnul ((const void *) str);
char *nd = strnul ((void *) str);
return (a != b) + (c != d) + (na != nb) + (nc != nd);
}