Proven C Book한국어 GitHub

78 What the new standards added, and the *_s controversy

What to know first

chapter 62, The standard library at a glance · the whole shap of the standard library
chapter 64, The traps of reading and writing · bounds and truncation

Looking back

Chapter 62 said the speed at which headers grow is the same as the language’s speed of change, and chapter 64 said gets’s funeral took twenty years. Then where are the “safe functions” the standard brought in to fill that place?

A. Mostly nowhere. C11 brought in dozens of functions such as gets_s and strcpy_s as annex K, but it was optional and the major implementations refused to adopt it. What was confirmed on the machine that made this book is the same — the “annex K: not present in this implementation” the example printed. The latter part of this chapter is that story.

The need for this chapter, and its context

Part 11 closes on a failed attempt. Told earlier, the Annex K story would have been an interesting anecdote; told now, after twenty-two chapters of meeting the standard functions’ traps in person, it reads as an answer to why fixing this is so hard. A fitting conclusion to a close reading.

By the end of this chapter

The last chapter of this part. We skim the headers C99, C11 and C23 added to the standard library, and then see the whole story of this language’s most famous failed attempt — annex K, which tried to bring “safe functions” into the standard. Why gets_s and strcpy_s are not widely used, and what is different about Microsoft’s functions of the same names.

The questions this chapter answers

  1. If annex K’s *_s functions failed, what fills their place now?

78.1 What C99 added

headerwhatits position today
<stdint.h>fixed-width integers (int32_t and so on)effectively compulsory. chapter 27
<inttypes.h>the format macros for those typesthe PRId32 family. appendix B
<stdbool.h>bool, true, falseunnecessary, having become keywords in C23
<complex.h>complex numbersoptional. support is uneven
<fenv.h>the floating-point environmentchapter 73
<tgmath.h>type-generic mathematicschapter 73

Table 79.1

<stdint.h> is this list’s winner. A standard way to express “exactly 32 bits” finally arose, and the practice of every project keeping its own typedef until then was tidied away.

Q. If annex K’s *_s functions failed, what fills their place now?

A. Not one thing but three, sharing it out. Compiler diagnostics (warnings and hardened builds such as _FORTIFY_SOURCE), sanitizers (chapter 17), and API designs that carry the length along. The last is the direction this book has pushed, and chapters 88 and 90 are its implementation.

The lesson is that safety does not arrive by appending _s to a function name. What actually worked was making failure impossible to ignore, putting the bounds inside the type, and making the checks something a tool can perform. That is how chapter 85 arranges the five bugs.

78.2 What C11 added

headerwhatits position today
<stdatomic.h>atomic operations and memory ordersthe foundation of concurrency. chapter 12′s story
<threads.h>threads, mutexes, condition variables★ adoption is slow — pthreads are usually used
<stdalign.h>alignas, alignofkeywords in C23
<stdnoreturn.h>noreturnto be retired in C23, in favour of [[noreturn]]
<uchar.h>char16_t, char32_tchapter 67

Table 79.2

<threads.h>‘s circumstance is interesting. Though it is in the standard, glibc long did not provide it, so portable code still uses POSIX threads. It is a case showing that entering the standard and becoming usable are different things.

78.3 What C23 added

examples-en/ch78/newheaders.c

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdckdint.h>
#include <stdalign.h>
#include <stddef.h>

int main(void)
{
    /* (1) is annex K really there — the implementation tells us */
#ifdef __STDC_LIB_EXT1__
    printf("annex K: present (__STDC_LIB_EXT1__)\n");
#else
    printf("annex K: not in this implementation\n");
#endif

    /* (2) C23's checked arithmetic — overflow reported as a value */
    int a = 2000000000, b = 2000000000, sum;
    if (ckd_add(&sum, a, b))
        printf("ckd_add: it overflowed (an int cannot hold it)\n");
    else
        printf("ckd_add: %d\n", sum);

    size_t count = (size_t)-1 / 2, elem = 8, bytes;
    if (ckd_mul(&bytes, count, elem))
        printf("ckd_mul: it overflowed — no allocation is attempted\n");

    /* (3) fixed-width integers and alignment */
    printf("int32_t=%zu bytes, alignof(max_align_t)=%zu\n",
           sizeof(int32_t), alignof(max_align_t));

    /* (4) C23's bool is a keyword — used without <stdbool.h> */
    bool ready = true;
    printf("bool is a keyword: %s\n", ready ? "true" : "false");

    /* (5) the edition confirmed through the macro the standard settles */
    printf("__STDC_VERSION__ = %ldL\n", (long)__STDC_VERSION__);
    return 0;
}

Output

annex K: not in this implementation
ckd_add: it overflowed (an int cannot hold it)
ckd_mul: it overflowed — no allocation is attempted
int32_t=4 bytes, alignof(max_align_t)=16
bool is a keyword: true
__STDC_VERSION__ = 202000L

<stdckdint.h> is this edition’s practical winner. It reports the wrap-round of the size calculations seen in chapter 65 as a valueckd_add, ckd_sub and ckd_mul return true on overflow, and the result may be treated as “unusable” rather than as the wrapped value.

<stdbit.h> is new too. Bit manipulations such as counting leading zeros, counting set bits and rounding up to a power of two have become standard functions — until then a place that leaned on compiler builtins (__builtin_clz and the like).

Besides these, C23 promoted bool, true, false, static_assert and thread_local to keywords, brought in nullptr (chapter 36), and effectively retired compatibility headers such as <stdbool.h> and <stdnoreturn.h>.

78.4 Annex K — the failed attempt at “safe functions”

Now the main business of this chapter.

In the early 2000s Microsoft put functions such as strcpy_s and sprintf_s into its compiler and began raising warnings on use of the existing functions. The proposal to make that design a standard entered C11 as annex K (bounds-checking interfaces).

The core ideas were three.

  1. The destination size is compulsorily taken as an argument.
  2. When a problem arises it does not truncate and carry on but returns an error (errno_t).
  3. When a contract violation is detected, the program’s chosen constraint handler is called.
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>

char dst[8];
errno_t e = strcpy_s(dst, sizeof dst, src);   /* an error if it overflows */

The direction resembles what we organised in chapter 62 as “what is needed”. Yet the result was a failure.

In practice. Why annex K was not adopted

In 2015, C standards committee document N1967, “Field Experience With Annex K”, surveyed the actual state. Its summary was cold — it was not widely implemented, it behaved differently where it was implemented, and there was no evidence that it made real code safer.

Concretely these were the circumstances.

  • Microsoft’s functions and the standard’s functions are not the same. The names are the same while arguments and behaviour go out of step in places, so code fitted to one side broke on the other.
  • Major implementations, glibc among them, did not adopt it. That is still so today — the reason the earlier example printed “not present in this implementation”.
  • The global state called the constraint handler caused conflicts between libraries.
  • It merely changed existing code mechanically, while the real defects remained where the size is calculated wrongly.

The committee went as far as discussing removing annex K, and in the end it was settled to be kept but effectively not recommended. It is a representative case showing how the expectation that “putting it in the standard makes things safe” goes wrong in reality.

A common misconception. “Using functions with _s attached is safe”

Three things must be checked. First, is that function there — the standard’s annex K is optional and is absent on most of the Unix family. Second, which edition is it — the standard’s and Microsoft’s may differ. Third, what becomes safe — it means the size is taken as an argument, not that the size you passed is right. The mistake of passing strlen instead of sizeof is just as much an accident in an _s function.

The realistic choice in portable code is still this — within the standard, snprintf and explicit bounds checking; where the platform permits, the strlcpy family; and for the repeated danger zones, components with checking built in (Part XII).

Platform note. The _s functions met on Windows

MSVC has long provided strcpy_s, sprintf_s, fopen_s and so on, and raises the C4996 warning when the existing functions are used. Defining _CRT_SECURE_NO_WARNINGS to turn the warning off is the practice.

The point to beware of is that these functions are not entirely the same as the standard’s annex K. For example the behaviour on argument-validation failure and the rules for return values may differ. So unless the code is Windows-only one does not lean on the _s family, and cross-platform projects mostly choose to keep a wrapper of their own.

78.5 What this part leaves behind

We have walked the standard library across ten chapters. Memorising function names was not the aim, so what remains to be kept is a few attitudes.

  1. Read the contract first. Does it take a size, how does it report failure, who owns the pointer it returned.
  2. Do not throw away return values. Especially fclose, snprintf and the scanf family.
  3. Suspect global state. errno, the locale, static buffers, the rounding mode.
  4. Being in the standard does not make it safe. gets survived twenty-two years.
  5. Weigh platform extensions between the gain and portability.

Recap

A table of the editions.

editionrepresentative additionis it actually used
C99<stdint.h>, <inttypes.h>yes — effectively compulsory
C99<complex.h>rarely
C11<stdatomic.h>yes — the foundation of concurrency
C11<threads.h>rarely — pthreads prevail
C11annex K (*_s)no — this chapter’s story
C23<stdckdint.h>yes — the right answer for size calculations
C23<stdbit.h>growing
C23keyword promotion (bool, nullptr and so on)yes

Table 79.3

The bottom three lines of that table — <stdatomic.h>, <stdckdint.h> and keyword promotion — have only shown their faces. The three remaining chapters of this part treat those three in detail, one each. We begin with the foundation of concurrency.