locale.h, wchar.h, wctype.h: Locale and Wide Characters
This chapter covers locale (<locale.h>), wide characters (<wchar.h>), and wide-character classification (<wctype.h>), including the wchar_t, char16_t, and char32_t types and UTF-8 interaction.
Why This Matters
Real programs must handle text beyond ASCII. Wide characters and locales are C's standard mechanism for that, but they are complex, platform-dependent, and frequently misunderstood in relation to UTF-8.
Prerequisites
c.core.23— strings and character functions.
Core Concept
Locale
A locale defines language/cultural conventions (character classification, number formatting, collation). setlocale(category, locale) changes it; localeconv() returns formatting details.
#include <locale.h>
setlocale(LC_ALL, ""); /* use the environment's locale */
Wide characters
wchar_t (from <stddef.h>/<wchar.h>) is a wide character type whose size is implementation-defined (16-bit on Windows, 32-bit on most Unix). Wide strings are arrays of wchar_t, terminated by a null wide character (L'\0').
wchar_t ws[] = L"hello";
<wchar.h> provides wide versions of string and I/O functions (wcscpy, wcscmp, wprintf, etc.).
char16_t and char32_t (C11)
<uchar.h> defines char16_t and char32_t for UTF-16 and UTF-32 code units, respectively. These are distinct from wchar_t.
UTF-8
UTF-8 is a byte encoding of Unicode where code points are represented by one to four bytes, and ASCII bytes never appear as part of a multibyte sequence (so embedded NULs are not an issue). UTF-8 is compatible with null-terminated char strings in the sense that strlen counts bytes (not characters), and '\0' terminates the string.
Syntax
#include <wchar.h>
#include <wctype.h>
#include <uchar.h>
#include <locale.h>
wchar_t c = L'A';
char16_t u16 = u'A';
char32_t u32 = U'A';
Examples
Wide string functions
#include <wchar.h>
#include <stdio.h>
int main(void)
{
wchar_t ws[32];
wcscpy(ws, L"hello");
wprintf(L"%ls\n", ws);
return 0;
}
Expected output: hello.
UTF-8 as bytes
#include <stdio.h>
#include <string.h>
int main(void)
{
const char *s = "héllo"; /* UTF-8 encoded */
printf("bytes: %zu\n", strlen(s)); /* counts bytes, not characters */
return 0;
}
The byte count depends on the encoding (UTF-8 "é" is 2 bytes).
How It Works
Wide characters store code points (or code units) in a fixed-width type, avoiding the variable-length encoding of UTF-8. Locale affects how multibyte and wide characters are converted (mbstowcs, wcstombs) and how classification works.
Variations
Multibyte vs. wide
C distinguishes multibyte strings (the normal char strings, which may use a locale-dependent encoding) from wide strings (wchar_t). Conversion functions (mbrtowc, wcrtomb) move between them.
UTF-8 and wchar_t
On most Unix systems, wchar_t is 32-bit and encodes Unicode code points directly; on Windows it is 16-bit (UTF-16). This is the key portability difference.
Common Mistakes
- Assuming
wchar_tis a fixed size everywhere. - Assuming
strlencounts characters (it counts bytes). - Mixing multibyte and wide strings without conversion.
- Assuming the default locale is UTF-8.
Undefined Behavior
- Passing invalid multibyte sequences to conversion functions can be UB or set
an error (EILSEQ), depending on the function.
Portability
wchar_tsize is implementation-defined.char16_t/char32_tare C11 and later.- UTF-8 is not required by ISO C, but it is the de facto standard encoding on
modern systems.
Under the Hood
Wide strings are arrays of fixed-width code units. Multibyte strings are byte arrays with variable-length encodings. Conversion functions implement the locale's encoding tables.
Practical Usage
- Prefer UTF-8
charstrings for portability and interop; treat them as byte
strings unless you need character-level operations.
- Use
wchar_tonly when the platform's wide-character model is required. - Use
<uchar.h>types for explicit UTF-16/UTF-32.
Exercises
1. Print the size of wchar_t, char16_t, and char32_t on your platform. 2. Demonstrate strlen counting bytes for a UTF-8 string. 3. Convert a multibyte string to wide and back using mbstowcs/wcstombs. 4. Set the locale and observe the effect on isalpha for non-ASCII input.
Deep Challenge
Write a small UTF-8 decoder that reads a char * string and produces char32_t code points, correctly handling continuation bytes and rejecting invalid sequences. Explain why wchar_t-based approaches are not portable here.
Related Concepts
c.core.23— strings.c.stdlib.5— ctype.h.c.net.4— byte order (analogous encoding concerns).
References
- ISO/IEC 9899:2018 §7.11 (locale.h), §7.29 (wchar.h), §7.30 (wctype.h),
§7.28 (uchar.h).
Verification
wchar_tsize is implementation-defined.VERIFIEDchar16_t/char32_tare C11.VERIFIEDstrlencounts bytes.VERIFIED- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Locale and setlocale
- [ ] wchar_t and wide strings
- [ ] char16_t/char32_t
- [ ] UTF-8 interaction
- [ ] Multibyte/wide conversion
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.lib.locale | 0 | 5 |
| c.lib.wchar | 0 | 5 |