Paul Eggert wrote:
> Although the function should be there, it cannot be
> const-generic in C.
Hmm. How useful would a function pointer to a function be,
if it has a different signature than if the function was invoked
directly? In this case, I'd prefer to not define the function
pointer at all.
> Although in some cases this wouldn't be quite as efficient as what I
> suggested earlier, it would be qualifier-generic if strchr is, and it
> would be simpler to implement.
strchr is a red herring, because this way to define strnul is compiled
less efficiently than (s + strlen (s)). See attached example code.
The better way is thus (s + strlen (s)), and since it evaluates s twice,
it means we need an inline function, not a macro. But we have the machinery
for inline functions in Gnulib.
I'm committing this implementation:
2026-02-21 Bruno Haible <[email protected]>
strnul: Add tests.
* tests/test-strnul.c: New file.
* tests/test-strnul-c++.cc: New file.
* modules/strnul-tests: New file.
* modules/strnul-c++-tests: New file.
strnul: New module.
Suggested by Alejandro Colomar <[email protected]> in
<https://lists.gnu.org/archive/html/bug-gnulib/2026-02/msg00121.html>.
* lib/string.in.h (gl_strnul): New function.
(strnul): New macro or template.
* lib/string.c: Update comment.
* m4/string_h.m4 (gl_STRING_H_REQUIRE_DEFAULTS): Initialize
GNULIB_STRNUL.
* modules/string-h (Makefile.am): Substitute GNULIB_STRNUL.
* modules/strnul: New file.
>From bb962a9cc89fbeca1eb4cea1b528c0647bb19b7a Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sat, 21 Feb 2026 10:26:13 +0100
Subject: [PATCH 1/2] strnul: New module.
Suggested by Alejandro Colomar <[email protected]> in
<https://lists.gnu.org/archive/html/bug-gnulib/2026-02/msg00121.html>.
* lib/string.in.h (gl_strnul): New function.
(strnul): New macro or template.
* lib/string.c: Update comment.
* m4/string_h.m4 (gl_STRING_H_REQUIRE_DEFAULTS): Initialize
GNULIB_STRNUL.
* modules/string-h (Makefile.am): Substitute GNULIB_STRNUL.
* modules/strnul: New file.
---
ChangeLog | 13 ++++++++++++
lib/string.c | 2 +-
lib/string.in.h | 52 ++++++++++++++++++++++++++++++++++++++++++++++++
m4/string_h.m4 | 3 ++-
modules/string-h | 1 +
modules/strnul | 23 +++++++++++++++++++++
6 files changed, 92 insertions(+), 2 deletions(-)
create mode 100644 modules/strnul
diff --git a/ChangeLog b/ChangeLog
index 43929ca5a2..749d269dc1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2026-02-21 Bruno Haible <[email protected]>
+
+ strnul: New module.
+ Suggested by Alejandro Colomar <[email protected]> in
+ <https://lists.gnu.org/archive/html/bug-gnulib/2026-02/msg00121.html>.
+ * lib/string.in.h (gl_strnul): New function.
+ (strnul): New macro or template.
+ * lib/string.c: Update comment.
+ * m4/string_h.m4 (gl_STRING_H_REQUIRE_DEFAULTS): Initialize
+ GNULIB_STRNUL.
+ * modules/string-h (Makefile.am): Substitute GNULIB_STRNUL.
+ * modules/strnul: New file.
+
2026-02-21 Bruno Haible <[email protected]>
streq: Rename to streq-opt.
diff --git a/lib/string.c b/lib/string.c
index c2e1b1f418..b8f079aa78 100644
--- a/lib/string.c
+++ b/lib/string.c
@@ -1,4 +1,4 @@
-/* streq and memeq functions.
+/* streq, memeq, gl_strnul functions.
Copyright (C) 2025-2026 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
diff --git a/lib/string.in.h b/lib/string.in.h
index 1b391507f4..599203c44d 100644
--- a/lib/string.in.h
+++ b/lib/string.in.h
@@ -1230,6 +1230,58 @@ _GL_WARN_ON_USE (strtok_r, "strtok_r is unportable - "
/* The following functions are not specified by POSIX. They are gnulib
extensions. */
+#if @GNULIB_STRNUL@
+/* Returns a pointer to the terminating NUL byte of STRING.
+ strnul (string)
+ This is a type-generic macro:
+ If STRING is a 'const char *', the result is 'const char *'.
+ If STRING is a 'char *', the result is 'char *'.
+ It is equivalent to
+ string + strlen (string)
+ or to
+ strchr (string, '\0'). */
+# ifdef __cplusplus
+extern "C" {
+# endif
+_GL_STRING_INLINE const char *gl_strnul (const char *string)
+ _GL_ATTRIBUTE_PURE
+ _GL_ARG_NONNULL ((1));
+_GL_STRING_INLINE const char *gl_strnul (const char *string)
+{
+ /* In gcc >= 7 or clang >= 4, we could use the expression
+ strchr (string, '\0')
+ because these compiler versions produce identical code for both
+ expressions. But this optimization in not available in older
+ compiler versions, and is also not available when the compiler
+ option '-fno-builtin' is in use. */
+ return string + strlen (string);
+}
+# ifdef __cplusplus
+}
+# endif
+# ifdef __cplusplus
+template <typename T> T strnul (T);
+template <> inline const char *strnul<const char *> (const char *s)
+{ return gl_strnul (s); }
+template <> inline char *strnul< char *> ( char *s)
+{ return const_cast<char *>(gl_strnul (s)); }
+# else
+# if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 9) > 4 && !defined __cplusplus) \
+ || (defined __clang__ && __clang_major__ >= 3) \
+ || (defined __SUNPRO_C && __SUNPRO_C >= 0x5150) \
+ || (__STDC_VERSION__ >= 201112L && !defined __GNUC__)
+/* The compiler supports _Generic from ISO C11. */
+# define strnul(s) \
+ _Generic (s, \
+ char * : (char *) gl_strnul (s), \
+ const char * : gl_strnul (s))
+# else
+# define strnul(s) \
+ ((char *) gl_strnul (s))
+# endif
+# endif
+#endif
+
#if @GNULIB_STR_STARTSWITH@
/* Returns true if STRING starts with PREFIX.
Returns false otherwise. */
diff --git a/m4/string_h.m4 b/m4/string_h.m4
index 2a6e4db46d..93a5d354cf 100644
--- a/m4/string_h.m4
+++ b/m4/string_h.m4
@@ -1,5 +1,5 @@
# string_h.m4
-# serial 46
+# serial 47
dnl Copyright (C) 2007-2026 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -73,6 +73,7 @@ AC_DEFUN([gl_STRING_H_REQUIRE_DEFAULTS]
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRSTR])
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRCASESTR])
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRTOK_R])
+ gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRNUL])
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STR_STARTSWITH])
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STR_ENDSWITH])
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MBSLEN])
diff --git a/modules/string-h b/modules/string-h
index 3e69040ea4..68ed79b462 100644
--- a/modules/string-h
+++ b/modules/string-h
@@ -68,6 +68,7 @@ string.h: string.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H
-e 's/@''GNULIB_STRNCPY''@/$(GNULIB_STRNCPY)/g' \
-e 's/@''GNULIB_STRNDUP''@/$(GNULIB_STRNDUP)/g' \
-e 's/@''GNULIB_STRNLEN''@/$(GNULIB_STRNLEN)/g' \
+ -e 's/@''GNULIB_STRNUL''@/$(GNULIB_STRNUL)/g' \
-e 's/@''GNULIB_STRPBRK''@/$(GNULIB_STRPBRK)/g' \
-e 's/@''GNULIB_STRSEP''@/$(GNULIB_STRSEP)/g' \
-e 's/@''GNULIB_STRSTR''@/$(GNULIB_STRSTR)/g' \
diff --git a/modules/strnul b/modules/strnul
new file mode 100644
index 0000000000..52d6729c8c
--- /dev/null
+++ b/modules/strnul
@@ -0,0 +1,23 @@
+Description:
+strnul() function: return pointer to terminating NUL byte.
+
+Files:
+lib/string.c
+
+Depends-on:
+string-h
+
+configure.ac:
+gl_STRING_MODULE_INDICATOR([strnul])
+
+Makefile.am:
+lib_SOURCES += string.c
+
+Include:
+<string.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
--
2.52.0
>From 0e87eaccbb996d00d6482dc259b2937a4ab24d9b Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sat, 21 Feb 2026 10:28:11 +0100
Subject: [PATCH 2/2] strnul: Add tests.
* tests/test-strnul.c: New file.
* tests/test-strnul-c++.cc: New file.
* modules/strnul-tests: New file.
* modules/strnul-c++-tests: New file.
---
ChangeLog | 6 ++++++
modules/strnul-c++-tests | 17 +++++++++++++++++
modules/strnul-tests | 12 ++++++++++++
tests/test-strnul-c++.cc | 17 +++++++++++++++++
tests/test-strnul.c | 37 +++++++++++++++++++++++++++++++++++++
5 files changed, 89 insertions(+)
create mode 100644 modules/strnul-c++-tests
create mode 100644 modules/strnul-tests
create mode 100644 tests/test-strnul-c++.cc
create mode 100644 tests/test-strnul.c
diff --git a/ChangeLog b/ChangeLog
index 749d269dc1..7681ab4c5d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
2026-02-21 Bruno Haible <[email protected]>
+ strnul: Add tests.
+ * tests/test-strnul.c: New file.
+ * tests/test-strnul-c++.cc: New file.
+ * modules/strnul-tests: New file.
+ * modules/strnul-c++-tests: New file.
+
strnul: New module.
Suggested by Alejandro Colomar <[email protected]> in
<https://lists.gnu.org/archive/html/bug-gnulib/2026-02/msg00121.html>.
diff --git a/modules/strnul-c++-tests b/modules/strnul-c++-tests
new file mode 100644
index 0000000000..2931df1cf1
--- /dev/null
+++ b/modules/strnul-c++-tests
@@ -0,0 +1,17 @@
+Files:
+tests/test-strnul-c++.cc
+
+Status:
+c++-test
+
+Depends-on:
+ansi-c++-opt
+
+configure.ac:
+
+Makefile.am:
+if ANSICXX
+TESTS += test-strnul-c++
+check_PROGRAMS += test-strnul-c++
+test_strnul_c___SOURCES = test-strnul-c++.cc
+endif
diff --git a/modules/strnul-tests b/modules/strnul-tests
new file mode 100644
index 0000000000..3d46b34a83
--- /dev/null
+++ b/modules/strnul-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-strnul.c
+tests/macros.h
+
+Depends-on:
+strnul-c++-tests
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-strnul
+check_PROGRAMS += test-strnul
diff --git a/tests/test-strnul-c++.cc b/tests/test-strnul-c++.cc
new file mode 100644
index 0000000000..3e6f19c33d
--- /dev/null
+++ b/tests/test-strnul-c++.cc
@@ -0,0 +1,17 @@
+/* Test of strnul() function in C++ mode.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+#include "test-strnul.c"
diff --git a/tests/test-strnul.c b/tests/test-strnul.c
new file mode 100644
index 0000000000..b9b4970ad6
--- /dev/null
+++ b/tests/test-strnul.c
@@ -0,0 +1,37 @@
+/* Test of strnul() function.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+/* Specification. */
+#include <string.h>
+
+#include "macros.h"
+
+int
+main ()
+{
+ const char ro[] = "foo";
+ char rw[] = "foo";
+
+ const char *ro_nul = strnul (ro);
+ char *rw_nul = strnul (rw);
+
+ ASSERT (ro_nul - ro == 3);
+ ASSERT (rw_nul - rw == 3);
+
+ return test_exit_status;
+}
--
2.52.0
#include <string.h>
const char *
strnul1 (const char *s)
{
return s + strlen (s);
}
const char *
strnul1b (const char *s)
{
return s + __builtin_strlen (s);
}
// Optimized like above by gcc >= 7 and clang >= 4
// Generates less well optimized code with option -fno-builtin
const char *
strnul2 (const char *s)
{
return strchr (s, '\0');
}
// Optimized like above by gcc >= 7 and clang >= 4
const char *
strnul2b (const char *s)
{
return __builtin_strchr (s, '\0');
}