Pali Rohár <[email protected]> wrote:
> On Thursday 18 June 2026 12:36:04 Kirill Makurin wrote:
>> Previous implementaion used `wcstombs` to convert its wide string arguments
>> to
>> narrow strings, which are then passed to `_assert`; function `wcstombs` is
>> thread-unsafe in its nature and should not be used.
>
> It is really truth that wcstombs is thread-unsafe? I had an impression
> that wcstombs is using its own thread local buffer, and that it is safe
> to use it (I used it many times in this way). So I would like to know
> more information.
I made this assumption because C89 mb* functions (mblen, mbtowc and mbstowcs)
are not threads-safe. What's interesting is that their _l versions seem to be
thread-safe, at least based on my tests.
I wrote a test program (attached), and it seems like C89 wc* functions (wctomb
and wcstombs) are really thread-safe on Windows. Even so, thread-safe code
should use `wcrtomb` and `wcsrtombs` instead.
I noticed that pre-msvcr80.dll's `w*printf` functions do not handle multibyte
narrow strings properly.
>> Other than thread-safety, `wcstombs` has other issues:
>>
>> 1. It uses code page used by active CRT locale; this may be an issue when
>> assert
>> message is displayed in message box, which assumes string to be encoded
>> using
>> active ANSI code page.
>
> Case 1. for sure when displaying to GUI MessageBoxA() it is needed to
> use ACP for conversion. But I still think that for fprintf output it
> should be used the CRT locale, not the ACP.
Absolutely; this is where we could use `_set_error_mode` to choose proper code
page.
>> 2. In case when entire string cannot be converted to target code page, it
>> does
>> not allow to convert only part of the string.
>
> This is also good point. WinAPI WideCharToMultiByte() can solve it. Or
> if we want to stick with CRT (which I think that it is a good idea for
> CRT functions) then loop over wcrtomb() could be used.
This is not an option when we convert to ACP, so I think using
`WideCharToMultiByte` is the simplest solution.
- Kirill Makurin
#define __USE_MINGW_ANSI_STDIO 0
#define _UNICODE
#define UNICODE
#include <assert.h>
#include <fcntl.h>
#include <io.h>
#include <locale.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifdef _WIN64
#define ZU L"%I64u"
#else
#define ZU L"%lu"
#endif
#ifdef _UCRT
#define SETLOCALE(c, l) _wsetlocale (c, l)
#define CREATE_LOCALE(c, l) _wcreate_locale (c, l)
#define LOCALE_STRING(locale, cp) TEXT (locale) TEXT (".65001")
#define EXPECTED_OUTPUT(input, ...) TEXT (input), input
#define EXPECTED_LENGTH(utf8, acp) utf8
#else
#define SETLOCALE(c, l) setlocale (c, l)
#define CREATE_LOCALE(c, l) _create_locale (c, l)
#define LOCALE_STRING(locale, cp) locale "." #cp
#define EXPECTED_OUTPUT(input, ...) TEXT (input), (const char[]) {__VA_ARGS__}
#define EXPECTED_LENGTH(utf8, acp) acp
#endif
typedef struct TestCase {
#ifdef _UCRT
const wchar_t *Locale;
#else
const char *Locale;
#endif
const wchar_t *In;
const char *ExpectedOut;
size_t ExpectedOutLength;
} TestCase;
static TestCase TestCase1 = {
LOCALE_STRING ("English_United States", 1252),
EXPECTED_OUTPUT ("Simple English text.", 'S', 'i', 'm', 'p', 'l', 'e', ' ',
'E', 'n', 'g', 'l', 'i', 's', 'h', ' ', 't', 'e', 'x', 't', '.', '\0'),
EXPECTED_LENGTH (20, 20)
};
static TestCase TestCase2 = {
LOCALE_STRING ("Japanese_Japan", 932),
EXPECTED_OUTPUT ("æ¥æ¬èªã", 0x93, 0xFA, 0x96, 0x7B, 0x8C, 0xEA, 0x81,
0x42, '\0'),
EXPECTED_LENGTH (12, 8)
};
#if defined _UCRT || __MSVCRT_VERSION__ >= 0x0800
#define USE_THREAD_LOCALE
#define TEST_CASE_PAIR(test1, test2) test1, test2
#else
#define TEST_CASE_PAIR(test1, test2) test2
#endif
static TestCase *TestCases[] = {
TEST_CASE_PAIR(&TestCase1, &TestCase2),
TEST_CASE_PAIR(&TestCase1, &TestCase2),
TEST_CASE_PAIR(&TestCase1, &TestCase2),
TEST_CASE_PAIR(&TestCase1, &TestCase2),
TEST_CASE_PAIR(&TestCase1, &TestCase2),
TEST_CASE_PAIR(&TestCase1, &TestCase2),
TEST_CASE_PAIR(&TestCase1, &TestCase2),
TEST_CASE_PAIR(&TestCase1, &TestCase2),
};
static LONG go = FALSE;
static DWORD __stdcall thread (void *arg) {
TestCase *test = (TestCase *) arg;
unsigned exit_code = 0;
#if defined USE_THREAD_LOCALE
_configthreadlocale (_ENABLE_PER_THREAD_LOCALE);
assert (SETLOCALE (LC_ALL, test->Locale) != NULL);
#elif defined USE_LOCALE_T
_locale_t locale = CREATE_LOCALE (LC_ALL, test->Locale);
assert (locale != NULL);
#endif
char buf[BUFSIZ];
size_t length;
while (InterlockedCompareExchange (&go, TRUE, TRUE) == FALSE) {
; // wait
}
#if defined USE_LOCALE_T
length = _wcstombs_l (buf, test->In, BUFSIZ, locale);
#else
length = wcstombs (buf, test->In, BUFSIZ);
#endif
if (length != test->ExpectedOutLength) {
fwprintf (
stderr, L"Call to wcstombs (buf, %s, BUFSIZ) returned " ZU L" when
expected " ZU L"\n",
test->In, (unsigned long long) length, (unsigned long long)
test->ExpectedOutLength
);
exit_code = 1;
}
if (memcmp (buf, test->ExpectedOut, test->ExpectedOutLength) == 0) {
#ifdef _UCRT
wprintf (L"%s: %S\n", test->Locale, buf);
#else
wprintf (L"%S: %S\n", test->Locale, buf);
#endif
} else {
#ifdef _UCRT
wprintf (L"%s: unexpected output\n", test->Locale);
#else
wprintf (L"%S: unexpected output\n", test->Locale);
#endif
}
return exit_code;
}
int main(void) {
int exit_code = EXIT_SUCCESS;
#if defined _UCRT || __MSVCRT_VERSION__ >= 0x0800
_setmode (_fileno (stdout), _O_U8TEXT);
_setmode (_fileno (stderr), _O_U8TEXT);
#endif
#ifdef USE_THREAD_LOCALE
_wsetlocale (LC_ALL, L"");
#else
SETLOCALE (LC_ALL, TestCases[0]->Locale);
#endif
HANDLE Threads[_countof (TestCases)];
for (size_t i = 0; i < _countof (TestCases); ++i) {
Threads[i] = CreateThread (NULL, 0, thread, TestCases[i], 0, NULL);
assert (Threads[i] != NULL);
}
go = TRUE;
WaitForMultipleObjects (_countof (TestCases), Threads, TRUE, INFINITE);
for (size_t i = 0; i < _countof (TestCases); ++i) {
DWORD thread_exit_code;
GetExitCodeThread (Threads[i], &thread_exit_code);
if (thread_exit_code != 0) {
exit_code = EXIT_FAILURE;
}
CloseHandle (Threads[i]);
}
return exit_code;
}_______________________________________________
Mingw-w64-public mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public