|
23 | 23 | 3. This notice may not be removed or altered from any source distribution. |
24 | 24 | */ |
25 | 25 |
|
| 26 | +/* Enable the POSIX-2008 thread-safe locale API (newlocale/strtod_l) where |
| 27 | + available; must precede the first system-header include (NrrdIO.h pulls in |
| 28 | + <stdlib.h>). NRRDIO_HAS_STRTOD_L selects a locale-independent numeric parse |
| 29 | + in airSingleSscanf(); platforms lacking it keep the historical sscanf(). */ |
| 30 | +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) \ |
| 31 | + || defined(__OpenBSD__) || defined(__DragonFly__) |
| 32 | +# include <xlocale.h> |
| 33 | +# include <pthread.h> |
| 34 | +# define NRRDIO_HAS_STRTOD_L 1 |
| 35 | +#elif defined(__GLIBC__) |
| 36 | +# ifndef _GNU_SOURCE |
| 37 | +# define _GNU_SOURCE 1 |
| 38 | +# endif |
| 39 | +# include <locale.h> |
| 40 | +# include <pthread.h> |
| 41 | +# define NRRDIO_HAS_STRTOD_L 1 |
| 42 | +#elif defined(_MSC_VER) |
| 43 | +# include <locale.h> |
| 44 | +# include <intrin.h> |
| 45 | +# define NRRDIO_HAS_STRTOD_L_WIN 1 |
| 46 | +#endif |
| 47 | + |
26 | 48 | #include "NrrdIO.h" |
27 | 49 |
|
| 50 | +#if defined(NRRDIO_HAS_STRTOD_L) |
| 51 | +/* Cached "C" locale for locale-independent strtod_l; created once. */ |
| 52 | +static locale_t _nrrdCLocale = (locale_t)0; |
| 53 | +static pthread_once_t _nrrdCLocaleOnce = PTHREAD_ONCE_INIT; |
| 54 | +static void |
| 55 | +_nrrdCLocaleInit(void) { |
| 56 | + _nrrdCLocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0); |
| 57 | +} |
| 58 | +static locale_t |
| 59 | +_nrrdGetCLocale(void) { |
| 60 | + pthread_once(&_nrrdCLocaleOnce, _nrrdCLocaleInit); |
| 61 | + return _nrrdCLocale; |
| 62 | +} |
| 63 | +#elif defined(NRRDIO_HAS_STRTOD_L_WIN) |
| 64 | +/* Cached "C" locale published exactly once via an atomic pointer CAS, so |
| 65 | + concurrent first use is race-free: the losing thread frees its duplicate. */ |
| 66 | +static void *volatile _nrrdCLocale = NULL; |
| 67 | +static _locale_t |
| 68 | +_nrrdGetCLocale(void) { |
| 69 | + _locale_t loc = (_locale_t)_nrrdCLocale; |
| 70 | + if (!loc) { |
| 71 | + _locale_t created = _create_locale(LC_NUMERIC, "C"); |
| 72 | + void *prev = _InterlockedCompareExchangePointer(&_nrrdCLocale, created, NULL); |
| 73 | + if (prev) { _free_locale(created); loc = (_locale_t)prev; } |
| 74 | + else { loc = created; } |
| 75 | + } |
| 76 | + return loc; |
| 77 | +} |
| 78 | +#endif |
| 79 | + |
28 | 80 | /* clang-format off */ |
29 | 81 | static const char * |
30 | 82 | boolStr[] = { |
@@ -105,12 +157,32 @@ airSingleSscanf(const char *str, const char *fmt, void *ptr) { |
105 | 157 | } else if (strstr(tmp, "inf")) { |
106 | 158 | val = (double)AIR_POS_INF; |
107 | 159 | } else { |
108 | | - /* nothing special matched; pass it off to sscanf() */ |
109 | | - /* (save setlocale here) */ |
| 160 | + /* nothing special matched; parse one floating-point value with a |
| 161 | + locale-independent "C" parse so NRRD's '.' decimal separator is |
| 162 | + honored regardless of the process/thread LC_NUMERIC setting. */ |
| 163 | +#if defined(NRRDIO_HAS_STRTOD_L) || defined(NRRDIO_HAS_STRTOD_L_WIN) |
| 164 | + char *endptr = NULL; |
| 165 | + double dval; |
| 166 | +# if defined(NRRDIO_HAS_STRTOD_L) |
| 167 | + locale_t cloc = _nrrdGetCLocale(); |
| 168 | + dval = cloc ? strtod_l(str, &endptr, cloc) : strtod(str, &endptr); |
| 169 | +# else |
| 170 | + _locale_t cloc = _nrrdGetCLocale(); |
| 171 | + dval = cloc ? _strtod_l(str, &endptr, cloc) : strtod(str, &endptr); |
| 172 | +# endif |
| 173 | + ret = (endptr != str) ? 1 : 0; |
| 174 | + if (ret) { |
| 175 | + if (fmt[1] == 'l') { *((double *)(ptr)) = dval; } |
| 176 | + else { *((float *)(ptr)) = AIR_FLOAT(dval); } |
| 177 | + } |
| 178 | + free(tmp); |
| 179 | + return ret; |
| 180 | +#else |
| 181 | + /* fallback: historical locale-dependent behavior, unchanged */ |
110 | 182 | ret = sscanf(str, fmt, ptr); |
111 | | - /* (return setlocale here) */ |
112 | 183 | free(tmp); |
113 | 184 | return ret; |
| 185 | +#endif |
114 | 186 | } |
115 | 187 | /* else we matched "nan", "-inf", or "inf"; now set val accordingly */ |
116 | 188 | if (fmt[1] == 'l') { |
|
0 commit comments