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');
}

Reply via email to