Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/MicrosoftDocs/cpp-docs/llms.txt

Use this file to discover all available pages before exploring further.

The Microsoft C Runtime Library introduced a set of security-enhanced functions in Visual Studio 2005, all bearing the _s suffix. These functions address well-known vulnerabilities in classic C string and I/O APIs — chiefly buffer overruns, NULL pointer dereferences, and malformed format strings. Understanding how they work, and how to migrate existing code to use them, is essential for writing safe and audit-compliant C and C++ applications with MSVC.

Why Classic CRT Functions Are Unsafe

Many foundational CRT functions were designed without buffer-size parameters, making it impossible for the runtime to detect overflows. When a source string is larger than the destination buffer, the function silently corrupts adjacent memory.
// UNSAFE: strcpy has no idea how large dest is
char dest[10];
strcpy(dest, "This string is longer than 10 characters!"); // Buffer overrun!

// UNSAFE: gets reads unlimited characters — removed from C11
char line[64];
gets(line); // Could overflow with any sufficiently long input

// UNSAFE: sprintf has no destination size guard
char buf[16];
sprintf(buf, "Value: %d", some_large_computation()); // May overflow
These patterns are the source of countless security vulnerabilities: stack smashing attacks, heap corruption, and remote code execution exploits. The CRT _s functions were designed to eliminate them.

How Security-Enhanced Functions Work

Secure functions take an explicit buffer-size parameter and perform three categories of validation before writing:
1

NULL Pointer Check

If any required pointer is NULL, the function immediately invokes the invalid parameter handler rather than dereferencing it.
2

Buffer Size Check

The function computes whether the requested operation would exceed the declared buffer size. If it would, it sets errno to ERANGE or EINVAL and invokes the invalid parameter handler.
3

Format String Validation

Functions like printf_s and sprintf_s reject format strings that contain %n (write-to-pointer directives), which are a common exploit vector.

Before and After: Migrating to Safe Functions

// BEFORE: unsafe
char dest[32];
strcpy(dest, input);   // No size check — potential overflow

// AFTER: secure
char dest[32];
errno_t err = strcpy_s(dest, sizeof(dest), input);
if (err != 0) {
    // Handle: input too long or NULL pointer
    fprintf(stderr, "strcpy_s failed: %d\n", err);
}

Common Secure Function Pairs

Unsafe FunctionSecure ReplacementKey Change
strcpystrcpy_sAdds destSize parameter
strcatstrcat_sAdds destSize parameter
sprintfsprintf_sAdds destSize parameter
vsprintfvsprintf_sAdds destSize parameter
sscanfsscanf_sAdds bufSize for %s/%c
getsgets_sAdds bufSize parameter
strtokstrtok_sAdds context pointer (thread-safe)
localtimelocaltime_sOutput buffer passed by caller
gmtimegmtime_sOutput buffer passed by caller
fopenfopen_sReturns errno_t, takes FILE**
tmpnamtmpnam_sAdds buffer size parameter
memcpymemcpy_sAdds destSize parameter
memmovememmove_sAdds destSize parameter

The Invalid Parameter Handler

When a secure function detects an invalid parameter (NULL pointer, buffer too small, invalid format), it does not simply return an error code — it calls the invalid parameter handler. By default, this handler terminates the process. You can install a custom handler using _set_invalid_parameter_handler.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Custom invalid parameter handler
void my_invalid_param_handler(
    const wchar_t* expression,
    const wchar_t* function,
    const wchar_t* file,
    unsigned int   line,
    uintptr_t      pReserved)
{
    wprintf(L"Invalid parameter in function %s, file %s, line %u\n",
            function ? function : L"<unknown>",
            file     ? file     : L"<unknown>",
            line);
    // Log the error, then optionally continue (for testing only)
    // In production, prefer terminating or throwing
}

int main(void) {
    _set_invalid_parameter_handler(my_invalid_param_handler);

    // This will trigger the handler: dest is too small for "Hello World!"
    char dest[5];
    errno_t err = strcpy_s(dest, sizeof(dest), "Hello World!");
    printf("strcpy_s returned: %d\n", err); // ERANGE

    return 0;
}
Returning from the invalid parameter handler is not recommended in production code. After the handler returns, the CRT may be in an undefined state. For production, log the error, flush critical data, and terminate the process.

Why You Should NOT Use _CRT_SECURE_NO_WARNINGS

When you first encounter deprecation warnings like C4996: 'strcpy': This function or variable may be unsafe, it is tempting to silence them with a preprocessor definition:
// DO NOT DO THIS in production code:
#define _CRT_SECURE_NO_WARNINGS   // Silences warnings without fixing them
#define _CRT_SECURE_NO_DEPRECATE  // Older equivalent
This suppresses the compiler’s warning without addressing the underlying safety problem. Your code remains vulnerable to buffer overruns; you have simply asked the compiler to stop telling you about it.
The correct approach is to migrate to _s variants. In C++ codebases, defining _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES as 1 activates secure template overloads that automatically redirect calls to unsafe functions to their safe equivalents, eliminating many warnings without manual code changes.
// C++ automatic overload approach: enables secure overloads for most functions
#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1
#include <string.h>

// Now this call automatically redirects to strcpy_s when dest is an array:
char dest[32];
strcpy(dest, "Safe now via template overload");
// Compiler replaces with: strcpy_s(dest, 32, "Safe now via template overload")

Debug Fill Behavior

In debug builds, security-enhanced CRT functions fill the portion of the output buffer that was not written with the value 0xFE. This helps detect cases where the caller passed an incorrect buffer size.
// Debug build: dest[6..31] will be filled with 0xFE after this call
char dest[32];
strcpy_s(dest, sizeof(dest), "Hello");
// dest[0..5] = "Hello\0"
// dest[6..31] = 0xFE 0xFE ... (debug builds only)
To disable this performance-impacting fill in debug builds, use _CrtSetDebugFillThreshold:
#include <crtdbg.h>
_CrtSetDebugFillThreshold(0); // Disable debug buffer fill entirely

Build docs developers (and LLMs) love