diff --git a/include/stdlib.h b/include/stdlib.h
index 114e12d255..a61b5683a5 100644
--- a/include/stdlib.h
+++ b/include/stdlib.h
@@ -35,6 +35,14 @@ extern __typeof (qsort_r) __qsort_r;
 libc_hidden_proto (__qsort_r)
 libc_hidden_proto (lrand48_r)
 libc_hidden_proto (wctomb)
+__typeof (mbstowcs) __mbstowcs attribute_hidden;
+
+/* Convert the multi-byte string S to a wide string.  Use
+   SCRATCH_COUNT wide characters at SCRATCH as storage if possible.
+   The caller must free *TO_FREE upon successful return.  */
+wchar_t *__mbstowcs_alloc (const char *s, wchar_t *scratch,
+			   size_t scratch_count, void **to_free)
+  attribute_hidden __nonnull ((1, 2, 4));
 
 extern long int __random (void) attribute_hidden;
 extern void __srandom (unsigned int __seed);
diff --git a/stdio-common/fxprintf.c b/stdio-common/fxprintf.c
index 8d02b71f91..b2cc270e71 100644
--- a/stdio-common/fxprintf.c
+++ b/stdio-common/fxprintf.c
@@ -16,6 +16,7 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <array_length.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -29,35 +30,15 @@ locked_vfxprintf (FILE *fp, const char *fmt, va_list ap)
   if (_IO_fwide (fp, 0) <= 0)
     return _IO_vfprintf (fp, fmt, ap);
 
-  /* We must convert the narrow format string to a wide one.
-     Each byte can produce at most one wide character.  */
-  wchar_t *wfmt;
-  mbstate_t mbstate;
-  int res;
-  int used_malloc = 0;
-  size_t len = strlen (fmt) + 1;
-
-  if (__glibc_unlikely (len > SIZE_MAX / sizeof (wchar_t)))
-    {
-      __set_errno (EOVERFLOW);
-      return -1;
-    }
-  if (__libc_use_alloca (len * sizeof (wchar_t)))
-    wfmt = alloca (len * sizeof (wchar_t));
-  else if ((wfmt = malloc (len * sizeof (wchar_t))) == NULL)
+  /* We must convert the narrow format string to a wide one.  */
+  wchar_t buffer[80];
+  void *to_free;
+  wchar_t *wfmt = __mbstowcs_alloc (fmt, buffer, array_length (buffer),
+                                    &to_free);
+  if (wfmt == NULL)
     return -1;
-  else
-    used_malloc = 1;
-
-  memset (&mbstate, 0, sizeof mbstate);
-  res = __mbsrtowcs (wfmt, &fmt, len, &mbstate);
-
-  if (res != -1)
-    res = _IO_vfwprintf (fp, wfmt, ap);
-
-  if (used_malloc)
-    free (wfmt);
-
+  int res = _IO_vfwprintf (fp, wfmt, ap);
+  free (to_free);
   return res;
 }
 
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 01194bbf7c..5f61f02d3a 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -40,7 +40,7 @@ routines	:=							      \
 	quick_exit at_quick_exit cxa_at_quick_exit cxa_thread_atexit_impl     \
 	abs labs llabs							      \
 	div ldiv lldiv							      \
-	mblen mbstowcs mbtowc wcstombs wctomb				      \
+	mblen mbstowcs mbstowcs_alloc mbtowc wcstombs wctomb		      \
 	random random_r rand rand_r					      \
 	drand48 erand48 lrand48 nrand48 mrand48 jrand48			      \
 	srand48 seed48 lcong48						      \
diff --git a/stdlib/mbstowcs.c b/stdlib/mbstowcs.c
index d8b91a73de..1d97aa02ef 100644
--- a/stdlib/mbstowcs.c
+++ b/stdlib/mbstowcs.c
@@ -24,7 +24,7 @@
    PWCS, writing no more than N.  Return the number written,
    or (size_t) -1 if an invalid multibyte character is encountered.  */
 size_t
-mbstowcs (wchar_t *pwcs, const char *s, size_t n)
+__mbstowcs (wchar_t *pwcs, const char *s, size_t n)
 {
   mbstate_t state;
 
@@ -32,3 +32,5 @@ mbstowcs (wchar_t *pwcs, const char *s, size_t n)
   /* Return how many we wrote (or maybe an error).  */
   return __mbsrtowcs (pwcs, &s, n, &state);
 }
+
+weak_alias (__mbstowcs, mbstowcs)
diff --git a/stdlib/mbstowcs_alloc.c b/stdlib/mbstowcs_alloc.c
new file mode 100644
index 0000000000..c6cb634303
--- /dev/null
+++ b/stdlib/mbstowcs_alloc.c
@@ -0,0 +1,64 @@
+/* mbstowcs with scratch buffer and fallback heap allocation.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+static wchar_t *
+mbstowcs_heap (const char *s, void **to_free)
+{
+  /* We assume that each byte produces at most one wide character.  */
+  size_t len = strlen (s) + 1;
+  wchar_t *buffer = __libc_reallocarray (NULL, len, sizeof (*buffer));
+  size_t res = __mbstowcs (buffer, s, len);
+  if (res == (size_t) -1)
+    {
+      free (buffer);
+      return NULL;
+    }
+  else if (res >= len)
+    {
+      /* The assumption that the length would not grow was not
+         true.  */
+      __set_errno (EILSEQ);
+      free (buffer);
+      return NULL;
+    }
+  *to_free = buffer;
+  return buffer;
+}
+
+wchar_t *
+__mbstowcs_alloc (const char *s, wchar_t *scratch, size_t scratch_count,
+                  void **to_free)
+{
+  *to_free = NULL;
+  size_t res = __mbstowcs (scratch, s, scratch_count);
+  if (res == (size_t) -1)
+    return NULL;
+
+  /* Check if the conversion fit into the scratch buffer.  The number
+     of converted characters does not included the null
+     terminator.  */
+  if (res < scratch_count)
+    return scratch;
+  else
+    return mbstowcs_heap (s, to_free);
+}
