Skip to content

Instantly share code, notes, and snippets.

@decagondev
Last active September 29, 2025 22:25
Show Gist options
  • Save decagondev/13b6ec127afcb4aafb9637cd436f803f to your computer and use it in GitHub Desktop.
Save decagondev/13b6ec127afcb4aafb9637cd436f803f to your computer and use it in GitHub Desktop.

scanf_s Family Functions Reference

Overview

The scanf_s family are secure versions of the standard scanf functions with enhanced security features to prevent buffer overruns. This guide focuses on sscanf_s and swscanf_s for parsing strings.


Function Variants

Function Description Character Type
sscanf_s Reads from narrow (char) string Single-byte characters
_sscanf_s_l Same as sscanf_s with locale parameter Single-byte characters
swscanf_s Reads from wide (wchar_t) string Wide characters
_swscanf_s_l Same as swscanf_s with locale parameter Wide characters

Syntax

swscanf_s (Wide Character Version)

int swscanf_s(
    const wchar_t *buffer,      // Input string to parse
    const wchar_t *format,      // Format specification string
    [argument] ...              // Variables to store parsed values
);

sscanf_s (Single-Byte Character Version)

int sscanf_s(
    const char *buffer,         // Input string to parse
    const char *format,         // Format specification string
    [argument] ...              // Variables to store parsed values
);

Parameters

buffer

The input string containing data to be parsed.

format

Format control string that specifies how to interpret the input. Contains:

  • Literal characters (must match exactly in input)
  • Format specifiers (start with %)
  • Whitespace (matches any amount of whitespace in input)

argument (optional)

Variables where parsed values will be stored. Must be pointers to the appropriate type.

locale (_l variants only)

The locale to use for parsing numbers and dates.


Return Value

Return Value Meaning
Positive integer Number of fields successfully converted and assigned
0 No fields were assigned
EOF Error occurred, or end of string reached before first conversion
-1 buffer or format is NULL (invalid parameter)

Important Notes

  • Return value does NOT include fields that were read but not assigned
  • A successful parse of "2023-01-01" with format "%4d-%2d-%2d" returns 3 (three integers parsed)

Format Specification Syntax

Basic Format Specifier Structure

%[*][width][length]type

Components

1. * (Assignment Suppression) - Optional

Reads the field but does NOT assign it to a variable.

int year;
swscanf_s(L"2023-01-01", L"%4d-*2d-*2d", &year);  
// Only year is stored, month and day are skipped

2. Width Specification - Optional but Recommended

Maximum number of characters to read for this field.

swscanf_s(L"2023", L"%4d", &year);  // Read max 4 digits

⚠️ For %c and %s, width specifies EXACT number of characters, not maximum!

3. Type Character - REQUIRED

See detailed type table below.


Common Type Specifiers

Type Description Example Input C Type Size Arg?
%d Decimal integer "123", "-456" int* No
%i Integer (auto-detects base) "0x1F", "077", "99" int* No
%u Unsigned decimal "123" unsigned int* No
%f Float/double "3.14", "1.5e-3" float* No
%s String (stops at whitespace) "hello" char* or wchar_t* YES
%c Character(s) "a" char* or wchar_t* YES
%x Hexadecimal integer "1A", "0xFF" int* No
%o Octal integer "77", "012" int* No

πŸ”’ Security: Buffer Size Requirements

Critical Difference from Non-Secure Versions

The _s (secure) versions REQUIRE a buffer size parameter for %c, %s, and character sets [].

General Rules

  1. For strings (%s): Buffer size includes the null terminator
  2. For characters (%c): Buffer size is the character count
  3. Size parameter type: unsigned int (NOT size_t)
  4. Size parameter position: Immediately after the buffer parameter

Practical Examples

Example 1: Parsing Date String

int year, month, day;
if (swscanf_s(L"2023-01-01", L"%4d-%2d-%2d", &year, &month, &day) == 3) {
    // Successfully parsed all three values
    // year = 2023, month = 1, day = 1
}

How it works:

  • %4d - Read up to 4 decimal digits β†’ year
  • - - Expect a literal hyphen character
  • %2d - Read up to 2 decimal digits β†’ month
  • - - Expect another literal hyphen
  • %2d - Read up to 2 decimal digits β†’ day
  • Returns 3 because three fields were assigned

Example 2: Reading Strings (Requires Size Parameter)

wchar_t name[50];
// CORRECT: Buffer size is 50 (includes null terminator)
if (swscanf_s(L"John", L"%s", name, (unsigned)50) == 1) {
    // name = L"John"
}

// With width specification (recommended)
if (swscanf_s(L"John", L"%49s", name, (unsigned)50) == 1) {
    // Reads max 49 chars, leaves room for null terminator
}

Example 3: Reading Single Characters

wchar_t ch;
// Size parameter is 1 (one character)
if (swscanf_s(L"A", L"%c", &ch, 1) == 1) {
    // ch = L'A'
}

// Reading multiple characters
wchar_t chars[4];
if (swscanf_s(L"ABCD", L"%4c", chars, 4) == 1) {
    // chars = {'A', 'B', 'C', 'D'}
    // NOTE: NOT null-terminated!
}

Example 4: Using _countof for Safety

char buffer[100];
// Best practice: use _countof to get array size
sscanf_s(input, "%99s", buffer, (unsigned)_countof(buffer));

Example 5: Mixed Data Types

wchar_t name[50];
int age;
float salary;

const wchar_t* input = L"Alice 30 55000.50";
int parsed = swscanf_s(input, L"%49s %d %f", 
                       name, (unsigned)_countof(name),
                       &age, 
                       &salary);

if (parsed == 3) {
    // name = L"Alice"
    // age = 30
    // salary = 55000.50
}

Example 6: Handling Invalid Input

int value;
int result = swscanf_s(L"invalid", L"%d", &value);

if (result == 0) {
    // No fields assigned - input doesn't match format
}
else if (result == EOF) {
    // Error or end of string reached
}

Common Pitfalls and Solutions

❌ Pitfall 1: Forgetting Size Parameter

// WRONG - Missing size parameter for %s
wchar_t name[50];
swscanf_s(L"John", L"%s", name);  // COMPILER ERROR!
// CORRECT
wchar_t name[50];
swscanf_s(L"John", L"%s", name, (unsigned)50);

❌ Pitfall 2: Using size_t Instead of unsigned

// WRONG - Using size_t on 64-bit systems
wchar_t name[50];
swscanf_s(L"John", L"%s", name, _countof(name));  // May fail!
// CORRECT - Cast to unsigned
wchar_t name[50];
swscanf_s(L"John", L"%s", name, (unsigned)_countof(name));

❌ Pitfall 3: Not Checking Return Value

// WRONG - Assuming parse succeeded
int year, month, day;
swscanf_s(dateStr, L"%4d-%2d-%2d", &year, &month, &day);
// Variables may contain garbage if parse failed!
// CORRECT - Always check return value
int year, month, day;
if (swscanf_s(dateStr, L"%4d-%2d-%2d", &year, &month, &day) == 3) {
    // Safe to use year, month, day
} else {
    // Handle error - invalid format
}

❌ Pitfall 4: Width vs Size Confusion for %c

// For %c, width specifies EXACT character count, not maximum
wchar_t c;
swscanf_s(L"AB", L"%c", &c, 1);  // Reads only 'A'
swscanf_s(L"AB", L"%2c", &c, 1); // WRONG - tries to read 2 chars into 1 space!

Comparison: sscanf vs sscanf_s

Feature sscanf sscanf_s
Buffer overflow protection ❌ No βœ… Yes
Requires size for %c, %s ❌ No βœ… Yes
Security Less secure More secure
Complexity Simpler syntax Requires size parameters
Recommended for new code ❌ No βœ… Yes

Format String Details

Whitespace in Format String

Any whitespace character in the format string matches any amount of whitespace in the input (including none).

int a, b;
// These are all equivalent:
swscanf_s(L"10 20", L"%d %d", &a, &b);
swscanf_s(L"10 20", L"%d%d", &a, &b);
swscanf_s(L"10    20", L"%d %d", &a, &b);  // Multiple spaces

Literal Characters

Non-whitespace, non-% characters must match exactly.

int year, month;
// Expects exactly "Year: XXXX Month: XX"
swscanf_s(L"Year: 2023 Month: 12", L"Year: %d Month: %d", &year, &month);

Error Handling

NULL Pointer Parameters

If buffer or format is NULL:

  1. Invalid parameter handler is invoked
  2. If execution continues, function returns -1
  3. errno is set to EINVAL

End of String

If end of string is reached before any conversion:

  • Returns EOF

Conversion Failures

If a field cannot be converted:

  • Parsing stops at that field
  • Returns count of successfully assigned fields
  • Failed field and remaining fields are not assigned

Best Practices

βœ… DO:

  • Always check the return value against expected field count
  • Use _countof() for array sizes and cast to (unsigned)
  • Use width specifications to limit input size
  • Initialize variables before parsing (in case of failure)
  • Use appropriate function for your character type (sscanf_s for char, swscanf_s for wchar_t)

❌ DON'T:

  • Don't forget size parameters for %c, %s, or []
  • Don't use size_t for size parameters (use unsigned)
  • Don't assume parsing succeeded without checking return value
  • Don't mix narrow and wide character functions/strings

Requirements

sscanf_s / _sscanf_s_l

  • Header: <stdio.h>
  • Minimum: Windows 2000 Professional

swscanf_s / _swscanf_s_l

  • Header: <stdio.h> or <wchar.h>
  • Minimum: Windows 2000 Professional

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment