C Mastery / locale.h, wchar.h, wctype.h: Locale and Wide Characters
Part 3 — The Standard Library

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

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

Undefined Behavior

an error (EILSEQ), depending on the function.

Portability

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

strings unless you need character-level operations.

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.

References

§7.28 (uchar.h).

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.lib.locale05
c.lib.wchar05