Functions that returns a pointer into a string passed argument, such as strchr,
ought to return
- a 'char *' if the argument is a 'char *',
- a 'const char *' if the argument is a 'const char *'.
This is now even mandated in ISO C 23 ยง 7.26.5.1.
As C functions, these take a 'const char *' as argument and return a
'char *' (because C doesn't have function overloading).
But the effect is that buggy code like this does not produce a warning
with "gcc -Wall":
=============================================
#include <string.h>
int main ()
{
const char *s = "hello world";
char *p = strchr (s, ' ');
*p = 'x';
return 0;
}
=============================================
And yet it crashes at runtime.
In order to make this code produce a warning, one needs to define
strchr as a macro:
#if __STDC_VERSION__ >= 202311
# define strchr(s,c) (typeof(s)) strchr ((s), (c))
#elif (__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) || (__clang_major__ >= 3) ||
defined __ICC || defined __TINYC__
# define strchr(s,c) _Generic ((s), char const *: (char const *) strchr ((s),
(c)), default: strchr ((s), (c)))
#endif
This uses the 'typeof' keyword from ISO C 23, or the '_Generic' keyword
for older compilers.
For C++, such a macro approach
#if __cplusplus >= 201103L
# define strchr(s,c) const_cast<decltype(s)>(strchr ((s), (c)))
#endif
should better be avoided, because it would not work when the first or second
argument contains template parameters that include a comma outside of
parentheses. So, for C++, the solution should be something with templates.
If someone has spare time available for this, please go ahead.
For the beginning, let me install this for 3 <string.h> functions.
This could be generalized to more functions:
strchr
strrchr
strchrnul
mbschr
mbsrchr
memchr
memrchr
rawmemchr
strstr
strcasestr
memmem
strpbrk
mbspbrk
2025-02-09 Bruno Haible <[email protected]>
mbsstr, mbscasestr, mbspcasecmp: Use const-improved function macros.
* lib/string.in.h (mbsstr, mbspcasecmp, mbscasestr): Define as macros
that cast the result to 'const char *' when the first argument is a
'const char *'.
* lib/mbsstr.c: Define _GL_NO_CONST_GENERICS.
* lib/mbscasestr.c: Likewise.
* lib/mbspcasecmp.c: Likewise.
diff --git a/lib/mbscasestr.c b/lib/mbscasestr.c
index 7a186e0f98..843229666f 100644
--- a/lib/mbscasestr.c
+++ b/lib/mbscasestr.c
@@ -15,6 +15,9 @@
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
+/* Don't use the const-improved function macros in this compilation unit. */
+#define _GL_NO_CONST_GENERICS
+
#include <config.h>
/* Specification. */
diff --git a/lib/mbspcasecmp.c b/lib/mbspcasecmp.c
index f57337f01e..0717cd893a 100644
--- a/lib/mbspcasecmp.c
+++ b/lib/mbspcasecmp.c
@@ -15,6 +15,9 @@
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
+/* Don't use the const-improved function macros in this compilation unit. */
+#define _GL_NO_CONST_GENERICS
+
#include <config.h>
/* Specification. */
diff --git a/lib/mbsstr.c b/lib/mbsstr.c
index 579bcd054d..8e8eb73d55 100644
--- a/lib/mbsstr.c
+++ b/lib/mbsstr.c
@@ -15,6 +15,9 @@
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
+/* Don't use the const-improved function macros in this compilation unit. */
+#define _GL_NO_CONST_GENERICS
+
#include <config.h>
/* Specification. */
diff --git a/lib/string.in.h b/lib/string.in.h
index ce48829900..6705967c59 100644
--- a/lib/string.in.h
+++ b/lib/string.in.h
@@ -1178,6 +1178,19 @@ _GL_CXXALIASWARN (mbsrchr);
_GL_EXTERN_C char * mbsstr (const char *haystack, const char *needle)
_GL_ATTRIBUTE_PURE
_GL_ARG_NONNULL ((1, 2));
+/* Don't silently convert a 'const char *' to a 'char *'. Programmers want
+ compiler warnings for 'const' related mistakes. */
+# if !(defined _GL_NO_CONST_GENERICS || defined __cplusplus)
+# if __STDC_VERSION__ >= 202311
+# define mbsstr(h,n) (typeof(h)) mbsstr ((h), (n))
+# elif ((__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) || (__clang_major__ >= 3) \
+ || defined __ICC || defined __TINYC__)
+# define mbsstr(h,n) \
+ _Generic ((h), \
+ char const *: (char const *) mbsstr ((h), (n)), \
+ default : mbsstr ((h), (n)))
+# endif
+# endif
#endif
#if @GNULIB_MBSCASECMP@
@@ -1219,6 +1232,19 @@ _GL_EXTERN_C int mbsncasecmp (const char *s1, const char
*s2, size_t n)
_GL_EXTERN_C char * mbspcasecmp (const char *string, const char *prefix)
_GL_ATTRIBUTE_PURE
_GL_ARG_NONNULL ((1, 2));
+/* Don't silently convert a 'const char *' to a 'char *'. Programmers want
+ compiler warnings for 'const' related mistakes. */
+# if !(defined _GL_NO_CONST_GENERICS || defined __cplusplus)
+# if __STDC_VERSION__ >= 202311
+# define mbspcasecmp(s,p) (typeof(s)) mbspcasecmp ((s), (p))
+# elif ((__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) || (__clang_major__ >= 3) \
+ || defined __ICC || defined __TINYC__)
+# define mbspcasecmp(s,p) \
+ _Generic ((s), \
+ char const *: (char const *) mbspcasecmp ((s), (p)), \
+ default : mbspcasecmp ((s), (p)))
+# endif
+# endif
#endif
#if @GNULIB_MBSCASESTR@
@@ -1230,6 +1256,19 @@ _GL_EXTERN_C char * mbspcasecmp (const char *string,
const char *prefix)
_GL_EXTERN_C char * mbscasestr (const char *haystack, const char *needle)
_GL_ATTRIBUTE_PURE
_GL_ARG_NONNULL ((1, 2));
+/* Don't silently convert a 'const char *' to a 'char *'. Programmers want
+ compiler warnings for 'const' related mistakes. */
+# if !(defined _GL_NO_CONST_GENERICS || defined __cplusplus)
+# if __STDC_VERSION__ >= 202311
+# define mbscasestr(h,n) (typeof(h)) mbscasestr ((h), (n))
+# elif ((__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) || (__clang_major__ >= 3) \
+ || defined __ICC || defined __TINYC__)
+# define mbscasestr(h,n) \
+ _Generic ((h), \
+ char const *: (char const *) mbscasestr ((h), (n)), \
+ default : mbscasestr ((h), (n)))
+# endif
+# endif
#endif
#if @GNULIB_MBSCSPN@