I'm fixing some Linux code which used strerror
(not thread-safe) for multi-threading. I found that strerror_r
and strerror_l
are both thread-safe. Due to different definitions for strerror_r
(depending on _GNU_SOURCE
it is differently defined) I'd like to use the newer strerror_l
function, but how am I supposed to obtain a locale_t
object for the current locale? I'm not using iconv
or anything, just plain libc, and I don't see how I can obtain a "default locale" object (I don't care in what language the error is printed, I just want a human readable string.)
If you pass "" to the locale parameter newlocale will allocate a locale object set to the current native locale[1]
[1]http://pubs.opengroup.org/onlinepubs/9699919799/functions/newlocale.html
static locale_t locale;
bool MyStrerrorInit(void)
{
locale = newlocale(LC_ALL_MASK,"",(locale_t)0);
if (locale == (locale_t)0) {
return false;
}
return true;
}
char * MyStrerror(int error)
{
return strerror_l(error, locale);
}
You could use POSIX uselocale
:
strerror_l(errno, uselocale((locate_t)0));
@TavianBarnes pointed out in the comment that this code can exhibit undefined behavior:
[CX] [Option Start] The behavior is undefined if the locale argument to strerror_l() is the special locale object LC_GLOBAL_LOCALE or is not a valid locale object handle. [Option End]
https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror.html
Upon successful completion, the uselocale() function shall return a handle for the thread-local locale that was in use as the current locale for the calling thread on entry to the function, or LC_GLOBAL_LOCALE if no thread-local locale was in use.
https://pubs.opengroup.org/onlinepubs/9699919799/functions/uselocale.html
Use of LC_GLOBAL_LOCALE with *_l() functions clarified the UB behavior of *_l()
functions when given LC_GLOBAL_LOCALE
as the locale
, provided a version without undefined behavior and discussed alternatives.
An obvious version that isn't thread safe:
locale_t locale = uselocale((locate_t)0);
if (locale == LC_GLOBAL_LOCALE) {
strerror(errno);
} else {
strerror_l(errno, locale);
}
A thread-safe version:
locale_t locale = uselocale((locate_t)0);
locale_t copy = loc;
if (copy == LC_GLOBAL_LOCALE) {
copy = duplocale(copy);
}
strerror_l(errno, copy);
if (loc == LC_GLOBAL_LOCALE) {
freelocale(copy);
}
Here's a full working example from @TavianBarnes.
NULL
is odd here. It's not NULL
, it's (locale_t)0
. uselocale((locale_t)0)
. Is locale_t
guaranteed to be a pointer? –
Ockham uselocale
up with setlocale
. I don't think locale_t
is guaranteed to be a pointer, except that it is in glibc. I've incorporated this into my answer. Thank you. –
Stung strerror(errno)
in the LC_GLOBAL_LOCALE
case. What you can do is duplocale()
and then pass it to strerror_l()
. Full working example: github.com/tavianator/bfs/blob/… –
Crown setlocale() + newlocale() + setlocale() + ...
approach. That discussion led to this POSIX issue which defined the behaviour of duplocale(LC_GLOBAL_LOCALE)
: austingroupbugs.net/view.php?id=301 –
Crown If you pass "" to the locale parameter newlocale will allocate a locale object set to the current native locale[1]
[1]http://pubs.opengroup.org/onlinepubs/9699919799/functions/newlocale.html
static locale_t locale;
bool MyStrerrorInit(void)
{
locale = newlocale(LC_ALL_MASK,"",(locale_t)0);
if (locale == (locale_t)0) {
return false;
}
return true;
}
char * MyStrerror(int error)
{
return strerror_l(error, locale);
}
© 2022 - 2024 — McMap. All rights reserved.