N3974 - Undefined Behavior in the C Programming Language with Examples

by David Svoboda svoboda@sei.cmu.edu

This is a paper that describes what is currently known about Undefined Behavior in C. The paper was created by the Undefined Behavior Study Group. The intention is for WG14 to publish this as a white paper. It will include examples of undefined behavior as applied to C23.

The intended audience for this paper is people who have some knowledge of C. The paper does not intend to explain basic C concepts. It does present code examples, and expects a basic understanding of C code, but will describe advanced concepts, such as the unreachable macro.

Undefined Behavior in the C Programming Language

Overview

This is an informative document that explains the concept of undefined behavior (UB) as used in the C Standard. The paper begins by defining undefined behavior, distinguishing it from other categories of behavior in the C Standard, such as implementation-defined and unspecified behavior. It examines concrete examples, the official standard definition, and the reasons undefined behavior arises in the language’s design, including cases where undefined behavior is later assigned implementation-defined behavior and the notion of unconditional undefined behavior.

Next, the paper examines why undefined behavior exists at all, and how it can be detected during development.

It then turns to the relationship between undefined behavior and compilers, exploring the contract between the standard, the programmer, and the implementation; the role of observability; and the restraint compilers do or do not exercise when undefined behavior is present.

The paper also addresses how undefined behavior affects programmers in practice, covering common misunderstandings such as the illusion of time travel optimizations, the apparent predictability of undefined behavior and the safety and security implications of undefined behavior in real world code.

Finally, the paper offers practical guidance for both audiences: advice for implementers on how to responsibly handle undefined behavior when building a conforming implementation, and advice for programmers on how to avoid and reason about undefined behavior.

What is Undefined Behavior?

Official Definition

The C Standard allows for certain behaviors to be undefined. Undefined behavior allows an implementation to either define platform-specific behaviors or ignore the possibility of an erroneous state. The language does not require a platform to diagnose these errors.

Subclause 3.5.3 of the C Standard [ISO/IEC 9899:2024] states that undefined behavior is:

behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this document imposes no requirements

Note 1 to entry: Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

Note 2 to entry: J.2 gives an overview over properties of C programs that lead to undefined behavior.

Note 3 to entry: Any other behavior during execution of a program is only affected as a direct consequence of the concrete behavior that occurs when encountering the erroneous or non-portable program construct or data. In particular, all observable behavior (5.1.2.4) appears as specified in this document when it happens before an operation with undefined behavior in the execution of the program.

Only the first section that ends with “for which this document imposes no requirements” is actually normative text that implementations are bound by. The following three notes are just examples of how implementations may implement undefined behavior, but an implementation is free to do anything it wants as the standard imposes “no requirements” whatsoever on the implementation.

Examples of Undefined Behaviour

Consider the following code:

int a[5];
a[x] = 0;

The array contains only 5 elements, so valid indices are 0 to 4. This means that writing to a[42] accesses memory outside the array’s bounds. This is undefined behavior, because the C standard does not define what happens in this case. It could appear to work, corrupt unrelated memory, crash the program, or behave unpredictably in any other way. The compiler is not required to check the bounds, and is free to assume this situation never occurs.

Another common example is dereferencing a null pointer:

int *p = NULL;
*p = 0;

This code also contains undefined behavior. The standard does not define what happens when you attempt to read from or write to the memory location that a null pointer refers to. In fact, the standard does not even acknowledge that a NULL pointer points to memory at all. In practice, this often causes the program to crash producing, for example, a segmentation fault on many platforms, but this is not guaranteed. The behavior could also appear to succeed, corrupt other memory, or do some very surprising things as we will discuss later in this document.

The standard contains many undefined behaviours and a full list of them can be found at the end of this document.

Other Types of Behavior in the C Standard.

Beyond undefined behavior, the C standard defines a range of terms related to behavior. Unlike undefined behavior, each of these terms define a constrained behavior that the implementation has some form of responsibility to uphold, even if it may vary between implementations.

All of these differ from undefined behavior in that, while they may produce different behaviors on different implementations, they do represent behaviors that a programmer can depend on, in a conforming C implementation.

Why does Undefined Behaviour Exist?

Undefined behavior is used in many places in the C standard and for various reasons such as:

Undefined Behavior that is Assigned an Implementation-Defined Behavior

The C standard states that any platform/implementation is free to detect undefined behavior and to provide platform/implementation-specific behavior and document this behavior if it wishes. In this sense, behavior undefined by the C Standard may be well-defined behavior on a particular implementation.

This can be useful, because it enables implementers to extend C’s capabilities, and thereby grants programmers access to platform-specific features. While the C language is designed to enable cross-platform development, programmers are free to only support or target a limited set of platforms. For example, there are implementations of C that define the behavior of out-of-bounds array writes, signed integer overflow, and null pointer dereferences.

Unless otherwise noted, this document considers undefined behavior only in cases where the implementation has not defined a platform-specific behavior or implementation-specific behavior.

Explicit versus Implicit Undefined Behavior

Undefined behavior can either be explicitly specified in the standard or remain implicit if the standard does not define a behavior. The ISO C working group’s charter [n3280] has, as an explicit goal:

Undefined behaviors, unspecified behaviors, implementation-defined behaviors, and other portability issues enumerated in Annex J of the Standard should be eliminated or reduced.

In addition to reducing undefined behavior, the committee has the additional task of maintaining the list of explicitly-specified undefined behaviors, as shown by Annex J.2.

Furthermore, there are paragraphs in the standard where it is unclear whether a behavior is defined or not. This can mean that some platforms/implementations treat a behavior as defined while others treat it as undefined. For example, consider paragraph 17 of subclause 6.7.3.2, which describes structure and union types:

Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.

It could be argued that this paragraph requires that the first member of a struct must have the same pointer address as the struct itself. As of C23, this argument has not been resolved, because the Standard provides no clearer text.

As another example, consider C99 [ISO/IEC 9899:1999], subclause 6.5.5 paragraph 6, which explains the behavior of the modulo operator %:

When integers are divided, the result of the / operator is the algebraic quotient with any fractional part discarded.90) If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a.

This paragraph neglects to address the corner case of INT_MIN % -1. In this case, the quotient INT_MIN / -1 is not representable as a signed int, as it is mathematically INT_MAX + 1, which is outside the range of signed ints. Consequently, INT_MIN % -1 was considered to implicitly be undefined behavior, even though the mathematical answer is 0. This was made explicit in C11 [ISO/IEC 9899:2011] :

When integers are divided, the result of the / operator is the algebraic quotient with any fractional part discarded.105) If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a; otherwise, the behavior of both a/b and a%b is undefined.

Detecting Undefined Behavior

Consider the following code:

int a[5];
a[x] = 0;

What should happen if x is 42? An implementation could issue an error, exit the program, or resize the array, among other choices. However, any of these choices would require the implementation to perform a test to see if the value is within the valid range of the array. A C implementation of such a test would be:

int a[5];
if (x < 0 || x >= 5) {
  /* Handle out-of-bounds write */
} else {
  a[x] = 0;
}

This range check would add work for the compiler and execution environment. Adding any requirement to detect if the assignment is out of bounds would come at a cost in runtime performance and increased complexity. Not only would the implementation have to check each access to the array, but it would also have to keep track of valid array ranges.

C is designed to be fast, simple, easily implementable, and generate optimal code, and this is why C does not require any detection of out-of-bounds states. Consequently, C cannot define a behavior for a state that is not detected. Therefore, the behavior must be undefined.

It is a common misconception that all undefined behavior in the standard stems from oversights, or from the ISO C working group’s failure to agree on an appropriate behavior. The above example clearly shows that it is not practical to define any consistent behavior for out-of-bounds array access without imposing considerable burden on the implementation to detect the state. The cost of detecting an erroneous state prevents the language from defining any behavior should it occur.

Furthermore, if the standard were to require a program to exit on an out-of-bounds write, then the following piece of code would become a valid way to exit a program:

int a[5];
a[24] = 0;

This is not a good way to deliberately exit a program. It is preferred that a program exit in a manner that the standard explicitly documents as exiting, such as by calling a function named exit.

Reconsider this code:

int a[5];
a[x] = 0;

Another interpretation of this code is that if there are no requirements for an implementation to handle an out-of-bounds access, then the code would contain an implicit contract that x can only be between 0 and 4. The implementation can then assume that the programmer is aware of the contract and consents to it, even if the implementation cannot by itself determine that the contract is valid by analysis of the possible values x may hold. The implementation therefore need not check the value of x.

If the programmer cannot guarantee that x is within range, they can always rewrite the code as:

int a[5];
if (x >= 0 && x < 5)
  a[x] = 0;

To conclude, many behaviors are undefined because detecting them may be difficult to do at compile time, or expensive in terms of runtime performance.

The existence of undefined behavior implies conversely that when a program has no undefined behavior, its behavior is well-specified by the ISO C standard and the platform/implementation that complies with ISO C. This is a promise or contract between the ISO C standard, the platform/implementation, and the programmer. If the program violates this promise, the program running on the platform can be anything, is likely to violate the programmer’s intentions, and will not be portable. We will call this promise the “Promise of Absence of Undefined Behavior”.

A C program that enters a state of undefined behavior can be considered to contain an error that the platform/implementation is under no obligation to catch or report, and the resulting execution is permitted by the standard to be anything.

What Does Undefined Behaviour Mean for Compilers and Compiler Improvisation?

Promise of Absence of Undefined Behavior: the Contract between the Standard, Programmer, and Implementation

Consider this code:

x = (x * 4) / 4;

From a mathematical perspective, this operation should not change the value of x. The multiplication and the division should cancel each other out. However, when calculated in a computer, x * 4 may result in a value that may not be expressed using the type of x. If x is an unsigned 32-bit integer with the value 2,000,000,000 and it is multiplied by 4, the operation could wrap on a 32-bit platform and produce 3,705,032,704. The subsequent division by 4 will then produce 926,258,176. Since the standard declares that operations on unsigned integers have defined modulo behavior, the two operations do not cancel each other out.

If we instead perform the same operation using signed integer types, things might change because signed integer overflow is undefined behavior. By using a signed integer, the programmer has agreed to the contract that no operations using the type will ever produce overflow. Therefore, the optimizer is free to ignore any potential overflow and can assume that the two operations cancel each other out. This mean that there is a significant optimization advantage in declaring that signed integer overflow is undefined behavior.

The promise that the program contains no undefined behavior is a powerful tool that compilers can employ to analyze code to find optimizations. Consider:

int a[5];
a[x] = 0;

If x is any value below 0 or above 4, the assignment has undefined behavior. On many platforms/implementations, a[-1] and a[5] would be assigned to addresses outside the bounds of a. Without requiring implementations to explicitly add bounds checks, it becomes impossible to predict the side effects of an out-of-bounds write. The implementation is therefore allowed to simply ignore the fact that undefined behavior can happen. By writing the above code, it can be argued that the programmer respects a contract with the compiler that x will never exceed the bounds of the array.

If we consider:

int a[5];
a[x] = 0;
if (x > 5) {
  // ...
}

In this case, because the assignment has undefined behavior if x is not between 0 and 4, the if statement can be translated as if the condition were always false. This allows the compiler to optimize away the if statement entirely. The reason is that any behavior which is changed by this optimization occurs when x is not between 0 and 4, and since it is undefined, any behavior by the platform is allowed. In this case, the optimization is allowed. This completely conforms to the standard, but it removes some predictability of undefined behavior and can make programs with undefined behavior much harder to debug. The out-of-bounds write no longer causes a predictable wild write and it also causes an if statement to be removed.

A common bug is to try to detect and avoid signed integer overflow with code like this:

if (x + 1 > x) {
  x++;
}

Because the if condition can only be false if there is undefined behavior, it can be translated as if it were always true. Consequently, many compilers will optimize away the if statement entirely.

The confluence of undefined behavior and more aggressive but standards-compliant compiler optimizations exposes latent bugs that may otherwise behave according to programmer intentions. These bugs are characterized as hard to find and diagnose. These bugs often do not appear at lower compiler optimization levels. This means that such bugs do not appear in executables that programmers typically produce during development. Consequently, these bugs can bypass many tests. Debuggers tend to operate on executables compiled with lower optimization settings, where many of these issues do not show up. This makes it harder to find and fix these bugs.

An early example of a vulnerability arising from such aggressive optimization is CERT vulnerability 162289.

Observability

The C standard describes program execution in terms of an abstract machine. This provides a semantic model for how a C program is defined to behave. The as-if rule allows an implementation to transform the program, provided that the resulting observable behaviour is preserved. Observable behaviour is the externally visible effects of execution, including input/output, changes to files, and accesses to volatile objects. The C standard specifies the requirements for these effects and states that they constitute the observable behaviour of the program. This means that the actual program behavior can vary radically depending on how an implementation is able to transform the program, as long as its observable behavior remains constant. For example, two non-observable operations can be reordered. Consider:

int a, b;
a = 0;
b = 1;

These are two non-observable assignments (because neither a nor b is volatile). As two independent operations they are not required to be executed in any particular order. They may in fact be executed concurrently. If we then consider:

*p = 0;
x = 42 / y;

These two operations are also non-observable operations but both operations can produce undefined behavior (either by p pointing to an invalid address, or y producing a divide by zero). Because the operations are non-observable, they may be reordered. The “as-if” rule permits a platform/implementation to execute the division before the assignment, as long as the re-ordering is not observable. If y is zero, there is no guarantee that *p is written before the program enters a state of undefined behavior.

Note that reordering is possible in this case if the optimizer can prove that p does not point to x or y.

Reaching the State of Undefined Behavior

A common consideration when discussing undefined behavior is the question of when undefined behavior is invoked. While some have argued that programs that are able to produce undefined behavior have no requirements whatsoever, it is the position of WG14 that a program must first reach a state of undefined behavior before the requirements of the language standard are suspended.

Consider the following:

int a[5], x;
scanf("%i", &x);
a[x] = 0;

In this example, a programmer-provided index is used to access an array of five elements. While this program may be bad form, it is well-defined until and unless scanf sets x to outside the range of the array. Perhaps the programmer has guaranteed that the index used to access the array will stay within the bounds of the array, but this guarantee is maintained outside of the program. Many programs depend on input strictly conforming to a set of requirements to operate correctly. While this may present safety and security issues, the programmers must weigh those considerations against other factors, such as performance. Even a strictly conforming program could enter a state of undefined behavior under some environmental circumstances. A program is only erroneous if it enters a state of undefined behavior. An implementation is not released from complying with the ISO C standard because undefined behavior is possible when executing that program; the implementation is released only once the program has entered a state of undefined behavior.

However, once the program has produced undefined behavior, it remains in a state of undefined behavior, where the ISO C standard imposes no constraints on the program does. This state of undefined behavior continues until the program terminates.

Unconditional Undefined Behavior

Consider this code:

int a[5];
a[42] = 0;

Every time this code runs, it will produce undefined behavior. The undefined behavior does not depend on any dynamic or external factors other than the code being executed. Let us call this type of undefined behavior as unconditional undefined behavior, because it only depends on variables that are known at compile time. The set of unconditional undefined behaviors is not well-defined because different implementations have differing abilities to detect undefined behavior at compile time. Consider:

void fn(int x) {
  int a[5];
  if (x > 0) {
    y = 42;
  } else {
    y = INT_MAX;
  }
  a[y] = 0;
}

This code also contains unconditional undefined behavior but requires a more complex analysis to reach that conclusion. The term “unconditional undefined behavior” denotes any undefined behavior that is not dependent on runtime state. An implementation is under no obligation to detect unconditional undefined behavior, but if an implementation does detect unconditional undefined behavior we have recommendations for how to proceed.

Compiler Restraint

Undefined behavior in C gives an implementation wide latitude to optimize the code. This freedom has enabled implementers to successively generate faster and faster machine code, which enables significant reduction in computing time and energy consumption for a wide range of workloads. C is the de facto benchmark for efficiency that other languages are compared against and strive to match.

Significant portions of undefined behavior, such as aliasing, provenance, and overflow of pointers are specifically designed to enable implementations to make optimizations. Violating these categories of undefined behavior is likely to cause unpredictable behavior only when an implementation engages with these opportunities to optimize code.

As many implementations support varying levels of optimizations, it is feared that compilers, at higher levels of optimizations, will ignore the C standard and break existing code. This is a misconception. Most C implementations are consistent with the C standard even at the highest levels of optimization settings. Optimizations often reveal existing bugs in the source code that are initially perceived as compiler bugs Regehr, Is That a Compiler Bug?. These bugs are usually in violation of the C standard even when the program operates consistently with the programmer’s expectations.

The higher a level of optimization is employed, the more bugs are exposed, but as the code is further transformed, it also becomes harder to debug. Many tools like debuggers depend on low levels of optimizations to be able to correctly associate the binary’s execution to the source code. This compounds the difficulty of diagnosing undefined behavior bugs.

Some bugs might be the programmer’s fault or might be the compiler’s fault or might be the optimizer’s fault. Unfortunately, C has a history of programmers, compiler writers, and optimizers blaming one another for introducing bugs. Undefined behavior is often used to blame the programmer, but it often does not resolve the argument. Consequently, many compilers avoid making certain optimizations, even when supported by the specification, if they anticipate a programmer backlash. For example, GCC, starting at version 4.2, introduced a -fno-strict-overflow flag which disabled optimizations that presumed that signed integer overflow is undefined behavior, based on the fact that allowing such optimizations broke many programs. This creates a gray area, where unsound code that contains undefined behavior may have an undocumented reliable or semi-reliable behavior on a particular platform. This gray area comes at the cost of denying performance afforded by the standard to compliant code.

How Are Programmers Affected by Undefined Behavior? Common Mistakes and Misunderstandings

Time Travel

Because any non-observable operation can be reordered or transformed, a program might reach a state of undefined behavior through an ordering not explicitly expressed in the source code. Due to the as-if rule, statements can be changed by undefined behavior before any actual undefined behavior is encountered during program execution. Consider:

int a[5];
if (x < 0 || x >= 5)
   y = 0;
a[x] = 0;

The implementation can determine that there will be undefined behavior if x is not between 0 and 4, and therefore the if statement can be removed as it only affects such cases. This can cause an out-of-order behavior known as “time traveling undefined behavior”, where a program bug causes unintended consequences before the undefined behavior is encountered during program execution. It is as if the undefined behavior traveled backwards in time from the array access to the if statement.

Time traveling undefined behavior is permitted if it does not interfere with observable behavior that occurs before entering a state of undefined behavior. Consider:

int a[5];
if (x < 0)
   y = 0;
if (x >= 5)
   printf("Error!\n");
a[x] = 0;

In this case, the call to printf is an observable event, and any re-ordering requires it to execute correctly unless it is preceded by a state of undefined behavior. The compiler is not permitted to optimize away the second if statement. The first if statement however has no impact on the observable behavior and can therefore be removed.

Note: Historically, there have been cases where time travel has impacted observable state (CVE-2009-2692, described by Goodin 2009). Implementers have generally considered these to be implementation bugs. To clarify that they indeed are bugs, the document [N3128 Uecker] was proposed and accepted for C23. It adds the non-normative “Note 3 to entry” that clarifies the issue in the standard.

Apparent Predictable Behavior of Undefined Behavior

Operating systems and even hardware have been designed to mitigate the side effects of unintentional undefined behavior, or deliberate sabotage using undefined behavior, with features such as protection of the memory containing the executable or execution stack. Due to some of these protections, some undefined behavior is predictably caught at runtime. This mitigates the unpredictable nature of undefined behavior and improves the stability and security of the system. However, this can also give the false impression that some undefined behavior has predictable side effects. While dereferencing null pointers is technically undefined behavior, doing so is defined to trap on some platforms, including POSIX. Even if the behavior of dereferencing null is reliable on a platform/implementation, the compiler’s assumption that the code will not dereference null will make it unreliable.

Some undefined behavior was initially included in the C standard because the standard wanted to allow for different platform/implementation designs. Over the years, some designs have grown so dominant that few programmers will ever encounter a platform/implementation that does not conform to these dominant designs. One example of this is two’s-complement arithmetic, which causes signed integer overflow to wrap.

This means that many undefined behaviors have predictable behavior on most platforms/implementations:

Undefined Behavior Convention
Dereferencing null pointer Traps
Signed integer overflow Wraps
Using the offset between 2 allocations Treats pointers as integer addresses
Comparing the pointer to freed memory with a newly allocated pointer Treats pointers as integer addresses

Such behavior is not defined by the C standard but can seem to be predictable. Predictability is of great value to most programmers. The knowledge of how the underlying platform operates lets the programmer predict and diagnose bugs. A trapped null pointer dereference is easy to find in a debugger. In fact, a programmer may deliberately add a null pointer dereference to a program to invoke a core dump. In MSVC’s debugger, uninitialized memory is set to 0xCDCDCDCD, a pattern that is instantly recognizable for any experienced Windows programmer. If the sum of two large positive signed integers results in a negative value, a wise programmer will suspect signed integer overflow which happened to wrap.

This apparent predictability of many types of undefined behavior hides the fact that undefined behavior is not predictable. This causes many programmers to either not realize that some of these behaviors are undefined or confuse undefined behavior with implementation-defined behavior. They may believe that undefined behavior is defined in the C standard and undefined behaviors may be non-portable, but they may assume that the behavior of their platform applies to all platforms, or other machines with same platform as their machine. This faulty assumption creates a variety of hard-to-diagnose issues that we will explore further.

For example, an out-of-bounds write may have a wide range of consequences as it can disturb many kinds of state. However, most programmers would assume that an out-of-bounds write is executed as a write operation, which is not true in general. As another example, if we consider another undefined behavior such as signed integer overflow, it is even less predictable that a simple arithmetic operation can have a wide range of unpredictable outcomes.

Safety and Security

C is regarded by some as an unsafe or insecure language. However, a language specification can neither be safe nor unsafe in itself. While a language could be designed to satisfy some particular definition of safety or security in mind, it is only language implementations that can enforce safety. The C standard does not require an implementation to check for several types of errors, but it also does not prevent an implementation from doing so. Hence, each implementation may choose the level of safety guaranteed.

The most popular implementations of C choose not to make many guarantees such as memory safety but instead choose to prioritize performance and power efficiency. There are safer implementations, but these are predominantly used to detect issues during development rather than to add additional protections to deployment. One such implementation is Valgrind, whose default tool “memcheck” detects out-of-bounds reads and writes to memory on the heap, as well as uninitialized reads, use-after-free errors, and memory leaks. Valgrind achieves these safety constraints at a significant performance cost. Many different implementations such as GCC, LLVM, and MSVS offer various tools for detecting and diagnosing undefined behavior. Several static analyzers also exist to alleviate this problem.

While not in any way mandated by the C specification, the prevailing modus operandi of C programmers consists of using safety-related tools to detect issues during development, rather than as backstops during deployment. A major drawback of this approach is that since undefined behavior is a state that often cannot be definitively detected until it occurs at runtime, there is no easy way to definitively guarantee that a program will not enter a state of undefined behavior.

Despite this, it is worth noting that some of the most trusted software in the world, like the Linux kernel, Apache, MySQL, Curl, OpenSSL and Git are written in C. The simplicity of C makes it significantly easier to read and detect issues.

C does suffer when the standard is unclear, particularly in areas of the memory model and concurrent execution. Rules about aliasing, active type, thread safety, and volatile leaves a lot open to interpretation as to what is undefined behavior, and what is not. On many of these issues there is a lack of consensus within WG14. Most implementations do support behaviors that in the strictest reading of the standard would be considered undefined behavior simply because of programmer expectation, and the ability to compile existing important software. In this sense most implementation deviate from the standard, but how and how much they deviate varies. Some projects like the Linux Kernel, have explicitly opted out of these ambiguities and defined their own requirements, such as disabling strict aliasing.

Undefined Behavior is Caused by Programmer Error

Note that this section applies only to undefined behavior that is not defined by a particular implementation.

Any statement that produces a state of undefined behavior (except for the unreachable macro) is erroneous, unless an implementation has defined its own behavior for that statement. An implementation is under no obligation to detect any undefined behavior. If, however, the implementation does not detect unconditional undefined behavior, it is free to assume the statement will not produce undefined behavior. Therefore, any unconditional undefined behavior (again, excepting unreachable) should be considered a programmer error and not an intended use of the language. In these cases, an implementation should produce a diagnostic message when it detects undefined behavior.

An implementation can assume that a program will not enter a state of undefined behavior, but no implementation should assume that a program that reaches a state of undefined behavior is intentional.

Consider again:

int a[5];
a[x] = 0;

The assignment may or may not produce undefined behavior. In this case if we follow the rule “assumed absence of undefined behavior”, we can assume that x must be between 0 and 4. The assignment is an assignment, but it also provides a hint to the compiler as to what x may be. If we then add:

int a[5];
a[x] = 0;
if (x > 4)
  ...

If the if statement here produces no observable behavior, then it can be considered dead code and optimized away. The if statement does not produce undefined behavior, it just cannot happen without undefined behavior. Now, consider:

int a[5];
if (x > 4) {
  a[x] = 0;
}

Again, this code may or may not trigger undefined behavior, but if the assignment is ever executed it is guaranteed to trigger undefined behavior. (Note that an implementation is not required to detect the undefined behavior.) In other words, the undefined behavior is unconditional, but only if the assignment is executed. See the section What is Undefined Behavior? for more information.

The correct interpretation of the detected unconditional undefined behavior is that the code is erroneous. It is incorrect to interpret the above code as a valid way for the programmer to express that x is 4 or less. The “assumed absence of undefined behavior” rule only applies to the way a construct can be assumed to be executed; it should not be taken to mean that a construct that always produces undefined behavior will never be executed. One divided by x lets the compiler assume that x is not zero, and x divided by zero should cause the compiler to assume unintended programmer error.

The one exception to this is the unreachable macro. The unreachable macro is the only way for a programmer to express that a statement can be assumed to never be executed. Incidentally, executing unreachable is undefined behavior, but it should not be regarded as equivalent to other undefined behavior in this regard.

For example:

if (x > 4)
  unreachable();

This is a correct way to express that a compiler can assume that x is smaller or equal to 4. Despite unreachable being undefined behavior, it is not equivalent to:

if (x > 4)
  x /= 0;

Division by zero is undefined behavior, but unlike unreachable, it is assumed to be a programmer error. The unreachable macro can therefore not be implemented by the programmer by producing undefined behavior in some way other than the unreachable macro. Undefined behavior is also erroneous even when it can be determined never to be executed. The following can be detected as erroneous:

if (0)
  x /= 0;

C is designed to make naive, as well as highly optimizing, implementations possible. The C standard therefore places no requirements or limits on the efforts an implementation might take to analyze the code. Whichever erroneous undefined behavior may be detected will therefore vary between implementations.

Advice for Implementers

We advise that implementations clearly document any language extensions that define any undefined behaviors so that programmers can differentiate between such extensions and seemingly predictable but still unintended behavior. Finally, if an implementation can detect unconditional undefined behavior, it should produce a diagnostic message (unless it provides a specific definition for that undefined behavior.)

A computer language is a tool for humans to communicate with computers, but it is also a tool for computers to communicate with humans. Humans spend more time reading the code they write and trying to figure out why its behavior does not match their expectations than computers do.

Traditionally, implementations have been black boxes that programmers must rely on without understanding how they operate. This approach causes issues, because modern compilers do not operate like many programmers expect them to. We therefore recommend that implementations try to find ways to be more transparent with their transformations. The ability for programmers to inspect code that has been transformed could reveal issues with ordering, code removal, load/store omissions and other non-obvious transformations. We recognize that this may involve significant user interface and architectural challenges.

Advice for Programmers

As this document has hopefully illustrated, undefined behavior in the context of C is complex. To simply say that certain guarantees have been omitted from the standard does not convey this complexity.

C is designed to be a language that trusts the programmer. In the case of undefined behavior, programmers should interpret this to mean “Trust the programmer not to initiate undefined behavior”, rather than “The programmer can trust undefined behavior if they know the underlying implementation and platform”. The ISO C working group therefore strongly advises programmers to avoid any undefined behavior, unless a platform/implementation has explicitly defined that behavior. Testing to determine what observable effect use of a non-portable or erroneous program construct has on your platform/implementation is insufficient cause for assuming the undefined behavior will consistently have the same behavior on all platform/implementation, including the next one that your code will run on. Only trust an implementation’s explicit documentation of a language extension that defines a behavior.

Examples of Undefined Behavior in the C Programming Language (C23)

For each undefined behavior, this document provides an example of code that demonstrates this undefined behavior.

The examples were initially produced via a Github project. In addition to the examples, that project contains several scripts to extract the code for compilation and testing.

Format

The format of this document is is:

Examples

1. A “shall” or “shall not” requirement that appears outside of a constraint is violated (Clause 4).

See UB Example #4, which violates a ‘shall’ statement.

2. A nonempty source file does not end in a new-line character which is not immediately preceded by a backslash character or ends in a partial preprocessing token or comment (5.2.1.2).

// Undefined Behavior
/* Comment with end comment marker unintentionally omitted
security_critical_function(void);

3. Token concatenation produces a character sequence matching the syntax of a universal character name (5.2.1.2).

#define assign(uc1, uc2, val) uc1##uc2 = val

void func(void) {
  int \u0401;
  // ...
  assign(\,u0401, 4);   // Undefined Behavior
  // ...
}

4. A program in a hosted environment does not define a function named main using one of the specified forms (5.2.2.3.2).

#include <stdio.h>

int main(float argc) {  // Undefined Behavior
  printf("main argument count:%f\n", argc); 
  return 0;
}

5. The execution of a program contains a data race (5.2.2.5).

#include <stdatomic.h>
#include <stdbool.h>

static _Bool flag;     // To eliminate undefined behavior, make this atomic

void init_flag(void) {
  flag = false;
}

// This function has a data race in the access of flag
// It also has a race condition in the read/modify/write sequence
// that is independent of the data race.
void toggle_flag(void) {
  flag = !flag;        // Undefined Behavior: data race
}

bool get_flag(void) {
  return flag;         // Undefined Behavior: data race
}

6. A character not in the basic source character set is encountered in a source file, except in an identifier, a character constant, a string literal, a header name, a comment, or a preprocessing token that is never converted to a token (5.3.1).

EXTENDED COMPILABLE EXAMPLE: Consider the following non-standard extension: a compiler that accepts the non-ASCII character: ÷ to act as a division operator. Consequently, in this compiler, the following code would be well-defined, but it is undefined behavior for compilers without this extension (and also a syntax error):

// In UTF-8: the division sign == '÷' == U+00F7
double x = 3 ÷ 7; // Undefined Behavior

7. An identifier, comment, string literal, character constant, or header name contains an invalid multibyte character or does not begin and end in the initial shift state (5.3.2).

/* In UTF-8 Lowercase n-with-tilde == ñ == U+00F1 == 0xC3 0xB1 == \303 \261 */

/* const char ma\303ana[] = "tomorrow"; */ /* invalid UTF-8, missing \261 */
/* Undefined Behavior if built in UTF-8 locale, even inside comment */

8. The same identifier has both internal and external linkage in the same translation unit (6.2.2).

int i1 = 10;         // Definition, external linkage
static int i2 = 20;  // Definition, internal linkage
extern int i3 = 30;  // Definition, external linkage
int i4;              // Tentative definition, external linkage
static int i5;       // Tentative definition, internal linkage

int i1;              // Valid tentative definition
int i2;              // Undefined Behavior, linkage disagreement with previous
int i3;              // Valid tentative definition
int i4;              // Valid tentative definition
int i5;              // Undefined Behavior, linkage disagreement with previous

Note: the following is a different example which is more complex:

static int a;
int f(void) {
  int a;
  {
     extern int a; // Undefined Behavior, not internal!
  }
}

9. An object is referred to outside of its lifetime (6.2.4).

void squirrel_away(char **ptr_param) {
  char local[10];
  // Initialize array ...
  *ptr_param = local;
}

void rodent(void) {
  char *ptr;
  squirrel_away(&ptr);
  char c = *ptr;  // Undefined Behavior: referred-to object no longer exists
}

10. The value of a pointer to an object whose lifetime has ended is used (6.2.4).

const char *p;
void dont_do_this(void) {
  const char c_str[] = "This will change";
  p = c_str; // Undefined Behavior if p subsequently read
}

void innocuous(void) {
  const char c_str[] = "Surprise, surprise";
  puts(c_str);
}

int main(void) {
  dont_do_this();
  innocuous();
  puts(p);  // On some platforms, this will print "Surprise, surprise"
  return EXIT_SUCCESS;
}

11. The value of an object with automatic storage duration is used while the object has an indeterminate representation (6.2.4, 6.7.11, 6.8).

int is_vowel(unsigned char c) {
  return strchr("aeiouy", c) != NULL;
}

int has_vowels(size_t max, unsigned char str[max]) {
  unsigned char c;
  for (size_t i = 0; i < max; i++) {
    if (is_vowel(c)) { // oops, should use str[i]!
      return 1;
    }
  }
  return 0;
}

CERT C Rule EXP33-C 4th NCCE 4.3.9, CERT C Rec MSC22-C 3rd NCCE

12. A non-value representation is read by an lvalue expression that does not have character type (6.2.6.1).

void f(size_t n) {
  int *a = malloc(n * sizeof(int));
  if (a != NULL) {
    for (size_t i = 0; i != n; ++i) {
      a[i] = a[i] ^ a[i]; // Undefined Behavior
    }
    // ...
    free(a);
  }
}

13. A non-value representation is produced by a side effect that modifies any part of the object using an lvalue expression that does not have character type (6.2.6.1).

union {
  float f;
  int i;
} u;

u.f = 3.14;
u.i++;                        // Undefined Behavior if u.i has non-value representation from previous statement.
printf("value is %f\n", u.f); // Undefined Behavior if u.f has non-value representation from previous statement.

14. Two declarations of the same object or function specify types that are not compatible (6.2.7).

// In a.c:
extern int i;  // Undefined Behavior

int f(void) {
  return ++i;
}


// In b.c:
short i;       // Undefined Behavior

15. A program requires the formation of a composite type from a variable length array type whose size is specified by an expression that is not evaluated (6.2.7).

void *p1(void), *p2(void);
int f(void), g(void);
int i = (f()
         ? ((int (*)[g()])p1())
         : ((int (*)[])p2()))[0][0];
// Undefined Behavior, g() may not be evaluated if f() returns 0

16. Conversion to or from an integer type produces a value outside the range that can be represented (6.3.2.4).

void func(float f_a) {
  int i_a;
  i_a = f_a;  // Undefined Behavior if the integral part of f_a cannot be represented.
}

17. Demotion of one real floating type to another produces a value outside the range that can be represented (6.3.2.5).

void func(double d_a, long double big_d) {
  double d_b = (float)big_d;  // Undefined Behavior, if outside range
  float f_a = (float)d_a;     // Undefined Behavior, if outside range
  float f_b = (float)big_d;   // Undefined Behavior, if outside range
}

18. An lvalue does not designate an object when evaluated (6.3.3.1).

void func(void) {
  int *p;
  {
    int i = 1;
    p = &i;
    *p = 2;  // ok
  }
  *p = 3;    // Undefined Behavior

  int a[10];
  a[1] = 4;  // ok
  a[10] = 5; // Undefined Behavavior
}

19. A non-array lvalue with an incomplete type is used in a context that requires the value of the designated object (6.3.3.1).

struct f *p;
void g(void) {
  *p;   // Undefined Behavior
}

20. An lvalue designating an object of automatic storage duration that could have been declared with the register storage class is used in a context that requires the value of the designated object, but the object is uninitialized. (6.3.3.1).

void f(void) {
  /* register */ int x;  // address of x not taken, so x could be stored in a register
  int y = x;             // Undefined Behavior
}

21. An lvalue having array type is converted to a pointer to the initial element of the array, and the array object has register storage class (6.3.3.1).

void f(void) {
  register int a[3];
  int *p = a;  // Undefined Behavior
  a[0] = 1;
  p[0];
}

22. An attempt is made to use the value of a void expression, or an implicit or explicit conversion (except to void) is applied to a void expression (6.3.3.2).

void f(int x) {
  printf("x is %d\n", x);
}

void *p = (void *) f;
int (*g)(int) = p;
int y = g(123);   // Undefined Behavior
printf("y is %d\n", y);

23. Conversion of a pointer to an integer type produces a value outside the range that can be represented (6.3.3.3).

void f(void) {
  char *ptr;
  // ...
  unsigned int number = (unsigned int)ptr;  // Undefined Behavior
  // ...
}

24. Conversion between two pointer types produces a result that is incorrectly aligned (6.3.3.3).

void f(void) {
  int *i_ptr;
  char c;
  i_ptr = (int *)&c;  // Undefined Behavior
  // ...
}

25. A pointer is used to call a function whose type is not compatible with the referenced type (6.3.3.3).

typedef char *(*fp_t)(void);
fp_t fp;
void f(void) {
  char *c;
  fp = (fp_t)strchr;  // from <string.h>
  c = fp();     // Undefined Behavior, incorrect arguments
}

26. An unmatched ’ or ” character is encountered on a logical source line during tokenization (6.4).

EXTENDED COMPILABLE EXAMPLE: Consider a platform that allows string literals to span multiple lines:

char s[] = "foo
bar";   // Undefined Behavior

27. A reserved keyword token is used in translation phase 7 or 8 (5.2.1.2) for some purpose other than as a keyword (6.4.2).

EXTENDED COMPILABLE EXAMPLE: Consider a platform that allows keywords to be used as identifiers:

int if = 3;  // Undefined Behavior

28. A universal character name in an identifier does not designate a character whose encoding falls into one of the specified ranges (6.4.3.1).

EXTENDED COMPILABLE EXAMPLE: Consider a platform that allows the division sign ÷ to be used as identifiers:

// In UTF-8: the division sign == '÷' == U+00F7
double ONE\u00F7TWO = 0.5;  // ONE ÷ TWO, Undefined Behavior

29. The initial character of an identifier is a universal character name designating a digit (6.4.3.1).

EXTENDED COMPILABLE EXAMPLE: Consider a platform that accepts universal character names that designate digits to be used as identifiers:

int U+FF11N = 1;  // "1N", Undefined Behavior (U+FF11 is the full width (CJK) numeral 1 and does not have the XID_Start property.)

30. Two identifiers differ only in nonsignificant characters (6.4.3.1).

// In bash/bashline.h:
extern char *bash_groupname_completion_function(const char *, int);
// Undefined Behavior, the identifier exceeds 31 characters


// In a.c:
#include <bashline.h>

void w(const char *s, int i) {
  bash_groupname_completion_function(s, i);
  // This function was taken from GNU Bash, version 3.2.
  // https://www.gnu.org/software/bash/
}


// In b.c:
int bash_groupname_completion_funct; 
// Undefined Behavior, identifier not unique within 31 characters

31. The identifier __func__ is explicitly declared (6.4.3.2).

void __func__(void); // Undefined Behavior

32. The program attempts to modify a string literal (6.4.6).

void f1(void) {
  char *p = "string literal";
  p[0] = 'S';  // Undefined Behavior
  // ...
}

33. The characters ’, \, “, //, or /* occur in the sequence between the < and > delimiters, or the characters ’, \, //, or /* occur in the sequence between the” delimiters, in a header name preprocessing token (6.4.8).

#include "dave's_hello.h"
// Undefined Behavior

34. A side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object (6.5.1).

#define CUBE(X) ((X) * (X) * (X))
 
void func(void) {
  int i = 2;
  int a = 81 / CUBE(++i);  // Undefined Behavior
  // ...
}

35. An exceptional condition occurs during the evaluation of an expression (6.5.1).

int add(void) {
  int x;
  scanf("%d", &x);  // Initialize x with an untrusted value, which could be INT_MAX
  return x + 1;     // Undefined Behavior
}

36. An object has its stored value accessed other than by an lvalue of an allowable type (6.5.1).

void func(void) {
  if (sizeof(int) == sizeof(float)) {
    float f = 0.0f;
    int *ip = (int *)&f;
    printf("float is %f\n", f);
    (*ip)++; // Undefined Behavior
    printf("float is %f\n", f);
  }
}

37. A function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function (6.5.3.3).

// In somefile.c:
long f(long x) {
  return x < 0 ? -x : x;
}

// In otherfile.c:
long f(int x);

int g(int x) {
  return f(x);  // Undefined Behavior
}

38. A member of an atomic structure or union is accessed (6.5.3.4).

_Atomic struct {
  int x;
} foo;
foo.x;   // Undefined Behavior

39. The operand of the unary * operator has an invalid value (6.5.4.3).

char *p = NULL;
*p;   // Undefined Behavior

40. A pointer is converted to other than an integer or pointer type (6.5.5).

struct f { struct f *x; } *p;
void g(void) {
  (struct f)p;   // Undefined Behavior
}

41. The value of the second operand of the / or % operator is zero (6.5.6).

int init(void) {return 0;}

int divide(int x) {
  int y = init(); // 0
  return x / y;   // Undefined Behavior
}

42. If the quotient a/b is not representable, the behavior of both a/b and a%b (6.5.6).

int init(void) {return -1;}

int remainder(int x) {
  int y = init(); // -1
  return x % y;   // Undefined Behavior
}

int f(void) {
  return remainder(INT_MIN);
}

43. Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that does not point into, or just beyond, the same array object (6.5.7).

#define TABLESIZE 100
static int table[TABLESIZE];

int *f(int index) {
  if (index < TABLESIZE) {  // doesn't check for negative values
    return table + index;   // Undefined Behavior
  }
  return NULL;
}

int *g(void) {
  return f(getc()); // could be EOF (-1)
}

44. Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that points just beyond the array object and is used as the operand of a unary * operator that is evaluated (6.5.7).

#define MAX_MACHINE_NAME_LENGTH 64
char *get_machine_name(const char *path) {
  char *machine_name = malloc(MAX_MACHINE_NAME_LENGTH + 1);
  if (machine_name == NULL) {
    return NULL;
  }

  while (*path != '\\') {
    *machine_name++ = *path++;  // Undefined Behavior, if \ not in path
  }

  *machine_name = '\0';
  return machine_name;
}

void f(void) {
  char* host = get_machine_name("foo");  // no '\'
  puts(host);
}

45. Pointers that do not point into, or just beyond, the same array object are subtracted (6.5.7).

enum { SIZE = 32 };
 
void func(void) {
  int nums[SIZE];
  int end;
  int *next_num_ptr = nums;
  size_t free_elements;

  /* Increment next_num_ptr as array fills */

  free_elements = &end - next_num_ptr;
}

CERT C Rule ARR36-C 1st NCCE 7.3.1

46. An array subscript is out of range, even if an object is apparently accessible with the given subscript (as in the lvalue expression a[1][7] given the declaration int a[4][5]) (6.5.7).

enum { COLS = 5, ROWS = 7 };
static int matrix[ROWS][COLS];

void init_matrix(int x) {
  for (size_t i = 0; i != COLS; ++i) {
    for (size_t j = 0; j != ROWS; ++j) {
      matrix[i][j] = x;  // Undefined Behavior, i and j swapped
    }
  }
}

47. The result of subtracting two pointers is not representable in an object of type ptrdiff_t (6.5.7).

#include <stdlib.h>

size_t size = 1 + (SIZE_MAX / 2);     // Assumes sizeof(size_t) == sizeof(ptrdiff_t)
char *x = malloc(size);
assert(x != NULL);
char *start = x;
char *too_far = x + size;
ptrdiff_t too_big = too_far - start;  // Undefined Behavior

48. An expression is shifted by a negative number or by an amount greater than or equal to the width of the promoted expression (6.5.8).

void func(unsigned int ui_a, unsigned int ui_b) {
  unsigned int uresult1 = ui_a << ui_b;
  // Undefined Behavior if ui_b >= UINT_WIDTH
  unsigned int uresult2 = ui_a << -1; // Undefined Behavior
}

49. An expression having signed promoted type is left-shifted and either the value of the expression is negative or the result of shifting would not be representable in the promoted type (6.5.8).

#include <limits.h>
#include <stddef.h>
#include <inttypes.h>

void func(signed long si_a, signed long si_b) {
  signed long result1;
  if (si_a > (LONG_MAX >> si_b)) {
    // Handle Error
  } else {
    result1 = si_a << si_b;  // Undefined Behavior if si_a or si_b < 0
  }
  unsigned int uresult2 = -1 << ui_b; // Undefined Behavior if ui_b != 0
}

CERT C Rule INT34-C 2nd NCCE 5.5.3

50. Pointers that do not point to the same aggregate or union (nor just beyond the same array object) are compared using relational operators (6.5.9).

struct {
  int x;
  int y;
} a, b;
if (&a.y < &b.y) {  // Undefined Behavior
  // ...
}

51. An object is assigned to an inexactly overlapping object or to an exactly overlapping object with incompatible type (6.5.17.2).

struct s { double i; } f(void);
union {
  struct {
    int f1;
    struct s f2;
  } u1;
  struct {
    struct s f3;
    int f4;
  } u2;
} g;

void func(void) {
  g.u2.f3 = g.u1.f2; // Undefined Behavior
}

52. An expression that is required to be an integer constant expression does not have an integer type; has operands that are not integer constants, named constants, compound literal constants, enumeration constants, character constants, predefined constants, sizeof expressions whose results are integer constants, alignof expressions, or immediately-cast floating constants; or contains casts (outside operands to sizeof and alignof operators) other than conversions of arithmetic types to integer types (6.6).

EXTENDED COMPILABLE EXAMPLE: Consider a platform that accepts floating-point constants to be used in constant integer expressions.

enum people {
  Tom=1.0,           // Undefined Behavior
  Dick=2,
  Harry=3
};

struct s {
  int bit:1;
  int two_bits:2.5;  // Undefined Behavior
  int all_the_bits;
};

Note: This UB has been eliminated by N3558.

53. A constant expression in an initializer is not, or does not evaluate to, one of the following: a named constant, a compound literal constant, an arithmetic constant expression, a null pointer constant, an address constant, or an address constant for a complete object type plus or minus an integer constant expression (6.6).

EXTENDED COMPILABLE EXAMPLE: Consider a platform that allows function calls to be used in constant integer expressions.

int square(int x) {
  return x*x;
}

enum people {
  Tom=1,
  Dick=2,
  Harry=square(2)           // Undefined Behavior
};

struct s {
  int bit:1;
  int two_bits:square(2);   // Undefined Behavior
  int all_the_bits;
};

Note: This UB has been eliminated by N3558.

54. An arithmetic constant expression does not have arithmetic type; has operands that are not integer constants, floating constants, named and compound literal constants of arithmetic type, character constants, predefined constants, sizeof expressions whose results are integer constants, or alignof expressions; or contains casts (outside operands to sizeof or alignof operators) other than conversions of arithmetic types to arithmetic types (6.6).

EXTENDED COMPILABLE EXAMPLE: Consider a platform that allows string literals to be used in constant arithmetic expressions.

float f = "Hello, world!";   // Undefined Behavior

Note: This UB has been eliminated by N3558.

55. The value of an object is accessed by an array-subscript [], member-access . or ->, address &, or indirection * operator or a pointer cast in creating an address constant (6.6).

const int primes[] = {2, 3, 5, 7};
const int *first_odd_prime = &(primes[1]); // Undefined Behavior

56. An identifier for an object is declared with no linkage and the type of the object is incomplete after its declarator, or after its init-declarator if it has an initializer (6.7).

Note: This UB has been eliminated by N3244.

57. A function is declared at block scope with an explicit storage-class specifier other than extern (6.7.2).

Note: This UB has been eliminated by N3244.

58. A structure or union is defined without any named members (including those specified indirectly via anonymous structures and unions) (6.7.3.2).

struct {
  int:3;    // Un-named bit-field
} myStruct; // Undefined Behavior

59. An attempt is made to access, or generate a pointer to just past, a flexible array member of a structure when the referenced object provides no elements for that array (6.7.3.2).

#include <stdlib.h>

struct S {
  size_t len;
  char buf[];  // Flexible array member
};

const char *find(const struct S *s, int c) {
  const char *first = s->buf;
  const char *last = s->buf + s->len;

  while (first++ != last) {   // Undefined Behavior
    if (*first == (unsigned char)c) {
      return first;
    }
  }
  return NULL;
}

void g(void) {
  struct S *s = malloc(sizeof(struct S));
  if (s == NULL) {
    // Handle Error
  }
  s->len = 0;
  find(s, 'a');
}

60. When the complete type is needed, an incomplete structure or union type is not completed in the same scope by another declaration of the tag that defines the content (6.7.3.4).

Note: This UB has been eliminated by N3244.

61. An attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type (6.7.4).

const int **ipp;
int *ip;
const int i = 42;

void func(void) {
  *ipp = &i;  // Valid
  *ip = 0;    // Undefined Behavior, modifies constant i (was 42)
  ipp = &ip;  // Constraint violation
}

62. An attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type (6.7.4).

#include <stdio.h>

void func(void) {
  static volatile int **ipp;
  static int *ip;
  static volatile int i = 0;

  printf("i = %d.\n", i);

  ipp = &ip;          // Undefined Behavior
  ipp = (int **) &ip; // Undefined Behavior
  *ipp = &i;          // Valid
  if (*ip != 0) {     // Valid
    // ...
  }
}

63. The specification of a function type includes any type qualifiers (6.7.4).

typedef int fun_t(int);
const fun_t f;  // Undefined Behavior

64. Two qualified types that are required to be compatible do not have the identically qualified version of a compatible type (6.7.4).

const struct s { int mem; } cs = { 1 };
typedef int A[2][3];
const A a = {{4, 5, 6}, {7, 8, 9}}; // array of array of const int
int *pi;
const int *pci;
pi = &cs.mem;  // Undefined Behavior: violates type constraints for =
pci = &cs.mem; // valid
pi = a[0];     // Undefined Behavior: a[0] has type "const int *"

65. An object which has been modified is accessed through a restrict-qualified pointer to a const-qualified type, or through a restrict-qualified pointer and another pointer that are not both based on the same object (6.7.4.2).

// This is the same function signature as memcpy()
void *my_memcpy(void * restrict s1, const void * restrict s2, size_t n) {
  memcpy( s1, s2, n);
}

void abcabc(void) {
  char c_str[]= "abc123edf";
  char *ptr1 = c_str;
  char *ptr2 = c_str + strlen("abc");
  my_memcpy(ptr2, ptr1, 6);   // Undefined Behavior, objects of ptr1 & ptr2 overlap
  puts(c_str);
}

CERT C Rule EXP43-C 2st NCCE 4.11.2.1, 3rd NCCE 4.11.2.3, 5th NCCE 4.11.4.1

66. A restrict-qualified pointer is assigned a value based on another restricted pointer whose associated block neither began execution before the block associated with this pointer, nor ended before the assignment (6.7.4.2).

int *restrict a;
int *restrict b;
extern int c[];

int main(void) {
  c[0] = 17;
  c[1] = 18;
  a = &c[0];
  b = &c[1];
  a = b;      // Undefined Behavior
  // ...
}

67. A function with external linkage is declared with an inline function specifier, but is not also defined in the same translation unit (6.7.5).

extern inline int foo(int x);  // Undefined Behavior

68. A function declared with a _Noreturn function specifier returns to its caller (6.7.5).

#include <stdnoreturn.h>

_Noreturn void f(void) {
  return;  // Undefined Behavior at run-time
}

69. The definition of an object has an alignment specifier and another declaration of that object has a different alignment specifier (6.7.6).

Note: This UB has been eliminated by N3244.

70. Declarations of an object in different translation units have different alignment specifiers (6.7.6).

// In file1.c:
alignas(16) int a;


// In file2.c:
alignas(32) int a; // Undefined Behavior

71. Two pointer types that are required to be compatible are not identically qualified, or are not pointers to compatible types (6.7.7.2).

int i = 1, j = 2;
const int *cp = &i; // *cp is constant
int *ncp = &j;      // *ncp is modifiable
ncp = cp;           // Undefined Behavior: violates modifiable lvalue constraint for =
cp = ncp;           // valid

72. The size expression in an array declaration is not a constant expression and evaluates at program execution time to a nonpositive value (6.7.7.3).

void func(int size) {
  // This creates a VLA if size > 0
  int arr[size];  // Undefined Behavior if size <= 0
  //  ... work with arr
}

int g(void) {
  func( getc()); // could be EOF (< 0)
}

73. In a context requiring two array types to be compatible, they do not have compatible element types, or their size specifiers evaluate to unequal values (6.7.7.3).

enum { ROWS = 10, COLS = 15 };
void func(void) {
  int a[ROWS][COLS];
  int (*b)[ROWS] = a;  // Undefined Behavior
}

74. A declaration of an array parameter includes the keyword static within the [ and ] and the corresponding argument does not provide access to the first element of an array with at least the specified number of elements (6.7.7.4).

const int size = 5;

int average(int numbers[static size]) {
  int sum = 0;
  for (int i = 0; i < size; i++) {
    sum += numbers[i];
  }
  return sum / size;
}

int main(void) {
  int a[3] = { 4, 1002, 27 };
  int result = average(a);   // Undefined Behavior, array too small
  printf("Average is %d\n", result);
}

75. A storage-class specifier or type qualifier modifies the keyword void as a function parameter type list (6.7.7.4).

void f(const void);
void g(register void);  // Undefined Behavior

76. In a context requiring two function types to be compatible, they do not have compatible return types, or their parameters disagree in use of the ellipsis terminator or the number and type of parameters (after default argument promotion, when there is no parameter type list) (6.7.7.4).

void fi(int *a) {
  (*a)++;
}

void fl(long *a) {
  (*a)++;
}

int repeat(void f(), int *a) {
  f(a);
  f(a);
  f(a);
}

int main (void) {
  int x = 1;
  repeat(fi, &x); // Undefined Behavior
  return 0;
}

77. The value of an unnamed member of a structure or union is used (6.7.11).

Note: This UB has been eliminated by N3245.

78. The initializer for a scalar is neither a single expression, an empty initializer, nor a single expression enclosed in braces (6.7.11).

Note: This UB has been eliminated by N3246.

79. The initializer for a structure or union object is neither an initializer list nor a single expression that has compatible structure or union type (6.7.11).

Note: This UB has been eliminated by N3246.

80. The initializer for an aggregate or union, other than an array initialized by a string literal, is not a brace-enclosed list of initializers for its elements or members (6.7.11).

EXTENDED COMPILABLE EXAMPLE: Consider a platform that allows integer literals to be used in aggregate initializer expressions.

struct st {
  int value;
};

struct st s = 0;   // Undefined Behavior

Note: This UB has been eliminated by N3346.

81. A function definition that does not have the asserted property is called by a function declaration or a function pointer with a type that has the unsequenced or reproducible attribute (6.7.13.8).

int next(int *ip) [[reproducible]] {
void adjust(unsigned *restrict x, unsigned *restrict y) [[reproducible]] {
    *x -= 3;  // Undefined Behavior, not reproducible
    *y += 2;  // Undefined Behavior, not reproducible
}

82. An identifier with external linkage is used, but in the program there does not exist exactly one external definition for the identifier, or the identifier is not used and there exist multiple external definitions for the identifier (6.9).

extern int x;

x = 3;   // Undefined Behavior if x is never defined
printf("x is %d!\n", x);

83. A function that accepts a variable number of arguments is defined without a parameter type list that ends with the ellipsis notation (6.9.2).

// In file1.c:
int add(int first, int second) {
  return first + second;
}


// In file2.c:
#include <stdio.h>

int add(int first, int second, ...);

int main (void) {
  int result = add(2, 3, 5, 7, 11, -1);   // Undefined Behavior
  printf("Sum is %d\n", result);
  return 0;
}

84. The } that terminates a function is reached, and the value of the function call is used by the caller (6.9.2).

#include <string.h>
#include <stdio.h>

int checkpass(const char *password) {
  if (strcmp(password, "pass") == 0) {
    return 1;
  }
  // Undefined Behavior, no return value
}

void func(const char *userinput) {
  if (checkpass(userinput)) {
    printf("Success\n");
  }
}

85. An identifier for an object with internal linkage and an incomplete type is declared with a tentative definition (6.9.3).

Note: This UB has been eliminated by N3347.

86. A non-directive preprocessing directive is executed (6.10).

# 1234
// Undefined Behavior

87. The token defined is generated during the expansion of a #if or #elif preprocessing directive, or the use of the defined unary operator does not match one of the two specified forms prior to macro replacement (6.10.2).

#define FOO 1

#define concat(uc1, uc2) uc1##uc2

#if concat(def, ined) FOO
// Undefined Behavior

88. The #include preprocessing directive that results after expansion does not match one of the two header name forms (6.10.3).

EXTENDED COMPILABLE EXAMPLE: Consider a platform that allows prefix and suffix commands associated with the inclusion of a file.

#include "hui.h" prefix(define pro 25) suffix(undef pro)
// Undefined Behavior

89. The #embed preprocessing directive that results after expansion does not match any of the name forms (6.10.4).

EXTENDED COMPILABLE EXAMPLE: Consider a platform that allows prefix and suffix commands associated with the embeddingn of a file.

// Embed a text file and manually append a null-terminator
const char message[] = {
#embed "message.txt" prefix(define pro 25) suffix(undef pro)
// Undefined Behavior
, '\0'
};

90. The character sequence in an #include preprocessing directive does not start with a letter (6.10.3).

#include "2file.h"
// Undefined Behavior

91. The character sequence in an #embed preprocessing directive does not start with a letter (6.10.4).

// Embed a text file and manually append a null-terminator
const char message[] = {
#embed "123message.txt"    // Undefined Behavior
, '\0'
};

92. There are sequences of preprocessing tokens within the list of macro arguments that would otherwise act as preprocessing directives (6.10.5).

#include <string.h>

void func(const char *src) {
  // Validate the source string; calculate size
  char *dest;
  // malloc() destination string
  memcpy(dest, src,
    #ifdef PLATFORM1
      12  // Undefined Behavior, if memcpy() defined as macro
    #else
      24
    #endif
  );
  // ...
);

93. The result of the preprocessing operator # is not a valid character string literal (6.10.5.3).

#define s(x) #x
char *x = s(\);  // Ill-formed, lone single quote

94. The result of the preprocessing operator ## is not a valid preprocessing token (6.10.5.4).

HYPOTHETICAL COMPILABLE EXAMPLE?

#define my_join(a, b) a ## b
char q[] = my_join(!, !);   // Ill-formed

95. The #line preprocessing directive that results after expansion does not match one of the two well-defined forms, or its digit sequence specifies zero or a number greater than 2147483647 (6.10.6).

#line 10000000000
// Undefined Behavior, > 2^31 - 1

96. A non-STDC #pragma preprocessing directive that is documented as causing translation failure or some other form of undefined behavior is encountered (6.10.8).

#pragma options align=foobar
// Undefined Behavior with GCC on Darwin platforms (e.g. Mac). See
// https://gcc.gnu.org/onlinedocs/gcc/Darwin-Pragmas.html

97. A #pragma STDC preprocessing directive does not match one of the well-defined forms (6.10.8).

#pragma STDC FP_CONTRACT FOOBAR
// Undefined Behavior

98. The name of a predefined macro, or the identifier defined, is the subject of a #define or #undef preprocessing directive (6.10.10).

#define __FILE__ source.c
// Undefined Behavior

99. An attempt is made to copy an object to an overlapping object by use of a library function, other than as explicitly allowed (e.g., memmove) (Clause 7).

#include <string.h>

void func(void) {
  char c_str[]= "test string";
  char *ptr1 = c_str;
  char *ptr2;

  ptr2 = ptr1 + 3;
  memcpy(ptr2, ptr1, 6);  // Undefined Behavior because of overlapping objects
}

100. A file with the same name as one of the standard headers, not provided as part of the implementation, is placed in any of the standard places that are searched for included source files (7.1.2).

#include <stdio.h>
// Undefined Behavior if a non-standard stdio.h exists in include path */

101. A header is included within an external declaration or definition (7.1.2).

struct st {
  int s;
#include <stdio.h>
// Undefined Behavior
};

102. A function, object, type, or macro that is specified as being declared or defined by some standard header is used before any header that declares or defines it is included (7.1.2).

#include <stdio.h>

double sin(double x) {
  return x - 3.0;
}

#include <math.h>
// Undefined Behavior, sin() already defined

int main(void) {
  double p = 3.14; // almost pi
  printf("sin( %f) is %f\n", p, sin(p));
  return 0;
}

103. A standard header is included while a macro is defined with the same name as a keyword (7.1.2).

#define sin int x;

#include <stdio.h>
// Undefined Behavior, sin() already defined

104. The program attempts to declare a library function itself, rather than via a standard header, but the declaration does not have external linkage (7.1.2).

#include <stddef.h>

static void *malloc(size_t nbytes); // Undefined Behavior

105. The program declares or defines a reserved identifier, other than as allowed by 7.1.4 (7.1.3).

// Identifiers that begin with _ are reserved at file scope.
static const unsigned int _max_limit = 1024; // Undefined Behavior
unsigned int _limit = 100; // Undefined Behavior
struct _data; // Undefined Behavior

/* Identifiers that begin with __ or _ followed by an uppercase letter
   are reserved for any use. */
unsigned int getValue(unsigned int __count) { // Undefined Behavior
  static unsigned int _CallCount; // Undefined Behavior
  _CallCount++;
  return __count < _limit ? __count : _limit;
}

106. The program removes the definition of a macro whose name begins with an underscore and either an uppercase letter or another underscore (7.1.3).

#undef __STDC_UTF_32__
// Undefined Behavior

107. An argument to a library function has an invalid value or a type not expected by a function with a variable number of arguments (7.1.4).

#include <stdio.h>

printf("Hello, %s!\n", 123);  // Undefined Behavior

108. The pointer passed to a library function array parameter does not have a value such that all address computations and object accesses are valid (7.1.4).

void f1(size_t nchars) {
  char *p = malloc(nchars);
  if (nchars == SIZE_MAX) {
    // Handle Error
  }
  const size_t n = nchars + 1;
  if (p) {
    memset(p, 0, n); // Undefined Behavior, 1-byte buffer overflow
    // ...
  }
}

109. The macro definition of assert is suppressed to access an actual function (7.2).

#include <assert.h>

typedef void (*handler_type)(int);

void execute_handler(handler_type handler, int value) {
  handler(value);
}

void func(int e) {
  execute_handler(&(assert), e < 0);  // Undefined Behavior
}

110. The argument to the assert macro does not have a scalar type (7.2).

#include <assert.h>

struct {
  int x;
  int y;
} s;
assert(s);  // Undefined Behavior

111. The CX_LIMITED_RANGE, FENV_ACCESS, or FP_CONTRACT pragma is used in any context other than outside all external declarations or preceding all explicit declarations and statements inside a compound statement (7.3.4, 7.6.2, 7.12.3).

void f(void) {
  float a = 4.0 / 7;

  #pragma STDC FP_CONTRACT OFF
  // Undefined Behavior

  a *= 7;
  printf("A is %f\n", a);
}

112. The value of an argument to a character handling function is neither equal to the value of EOF nor representable as an unsigned char (7.4).

void count_preceding_whitespace(const char *s) {
  const char *t = s;
  size_t length = strlen(s) + 1;

  while (isspace(*t) && (t - s < length)) {  // Undefined Behavior, if char is signed and *t = -2
    ++t;
  }

  int c = UCHAR_MAX + 1;
  int result = isspace(c);                    // Undefined Behavior
}

113. A macro definition of errno is suppressed to access an actual object, or the program defines an identifier with the name errno (7.5).

extern int errno;  // Undefined Behavior

CERT C Rule MSC38-C 2nd NCCE 15.5.3

114. Part of the program tests floating-point status flags, sets floating-point control modes, or runs under non-default mode settings, but was translated with the state for the FENV_ACCESS pragma “off” (7.6.2).

#pragma STDC FENV_ACCESS OFF

int set_excepts;
feclearexcept(FE_INVALID);
set_excepts = fetestexcept(FE_INVALID); // Undefined Behavior
if (set_excepts & FE_INVALID) {
  puts("INVALID");
}

115. The exception-mask argument for one of the functions that provide access to the floating-point status flags has a nonzero value not obtained by bitwise OR of the floating-point exception macros (7.6.5).

#pragma STDC FENV_ACCESS ON
int set_excepts = fetestexcept(1); // Undefined Behavior

116. The fesetexceptflag function is used to set floating-point status flags that were not specified in the call to the fegetexceptflag function that provided the value of the corresponding fexcept_t object (7.6.5.6).

#pragma STDC FENV_ACCESS ON

fexcept_t excepts;
fesetexceptflag(&excepts,FE_ALL_EXCEPT);
// Undefined Behavior, excepts not set by fegetexceptflag

117. The argument to fesetenv or feupdateenv is neither an object set by a call to fegetenv or feholdexcept, nor is it an environment macro (7.6.7.4, 7.6.7.5).

#pragma STDC FENV_ACCESS ON

fenv_t fenv;
fesetenv(&fenv);
// Undefined Behavior, fenv not initialized

118. The value of the result of an integer arithmetic or conversion function cannot be represented (7.8.3.1, 7.8.3.2, 7.8.3.3, 7.8.3.4, 7.24.7.1, 7.24.7.2, 7.24.2).

intmax_t x = INTMAX_MIN;
intmax_t px = imaxabs(x);  // Undefined Behavior

119. The program modifies the string pointed to by the value returned by the setlocale function (7.11.2).

void f1(void) {
  char *locale = setlocale(LC_ALL, 0);
  if (locale != NULL) {
    char *cats[8];
    char *sep = locale;
    cats[0] = locale;
    int i;

    if (sep) {
      for (i = 0; (sep = strstr(sep, ";:")) && i < 8; ++i) {
        *sep = '\0';   // Undefined Behavior
        cats[i] = ++sep;
      }
    }
  }
}

120. A pointer returned by the setlocale function is used after a subsequent call to the function, or after the calling thread has exited (7.11.2).

#include <locale.h>

char *locale1 = setlocale(LC_ALL, "");
assert(locale1);

// ...

char *locale2 = setlocale(LC_ALL, "");
int size = strlen(locale1);  // Undefined Behavior

121. The program modifies the structure pointed to by the value returned by the localeconv function (7.11.3.1).

void f2(void) {
  struct lconv *conv = localeconv();
  if ('\0' == conv->decimal_point[0]) {
    conv->decimal_point = ".";   // Undefined Behavior
  }
  if ('\0' == conv->thousands_sep[0]) {
    conv->thousands_sep = ",";   // Undefined Behavior
  }
}

122. A macro definition of math_errhandling is suppressed or the program defines an identifier with the name math_errhandling (7.12).

#include <math.h>

int math_errhandling;  // Undefined Behavior

123. An argument to a floating-point classification or comparison macro is not of real floating type (7.12.4, 7.12.18).

#include <math.h>
#include <complex.h>

double complex p = CMPLX( 2, 3); // 2 + 3i
if (isfinite(p)) {  // Undefined Behavior
  // ...
}

124. A macro definition of setjmp is suppressed to access an actual function, or the program defines an external identifier with the name setjmp (7.13).

#include <setjmp.h>

int setjmp(char *foo);  // Undefined Behavior

125. An invocation of the setjmp macro occurs other than in an allowed context (7.13.3.1).

jmp_buf buf;

void f(void) {
  int i = setjmp(buf);  // Undefined Behavior
  if (i == 0) {
    g();
  } else {
    // longjmp was invoked
  }
}

void g(void) {
  // ...
  longjmp(buf, 1);
}

126. The longjmp function is invoked to restore a nonexistent environment (7.13.3.1).

#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>

static jmp_buf buf;
static void bad(void);

static void g(void) {
  if (setjmp(buf) == 0) {
    printf("setjmp() invoked\n");
  } else {
    printf("longjmp() invoked\n");
  }
}

static void f(void) {
  g();
}

static void setup(void) {
  f();
}

void do_stuff(void) {
  void (*b)(void) = bad;
  // ...
  longjmp(buf, 1);   // Undefined Behavior
}

static void bad(void) {
  printf("Should not be called!\n");
  exit(1);
}

int main(void) {
  setup();
  do_stuff();
}

127. After a longjmp, there is an attempt to access the value of an object of automatic storage duration that does not have volatile-qualified type, local to the function containing the invocation of the corresponding setjmp macro, that was changed between the setjmp invocation and longjmp call (7.13.2.1).

jmp_buf buf;

void f(void) {
  int i = 0;
  if (setjmp(buf) != 0) {
    printf("%i\n", i);      // Undefined Behavior
    // ...
  }
  i = 2;
  g();
}

void g(void) {
  // ...
  longjmp(buf, 1);
}

128. The program specifies an invalid pointer to a signal handler function (7.14.2.1).

#include <signal.h>

void *handler = NULL;
signal(SIGALRM, handler);  // Undefined Behavior

129. A signal handler returns when the signal corresponded to a computational exception (7.14.2.1).

#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>

volatile sig_atomic_t denom;

void sighandle(int s) {
  // Fix the offending volatile
  if (denom == 0) {
    denom = 1;
  }
}

int main(int argc, char *argv[]) {
  if (argc < 2) {
    return 0;
  }

  char *end = NULL;
  long temp = strtol(argv[1], &end, 10);

  if (end == argv[1] || 0 != *end ||
     ((LONG_MIN == temp || LONG_MAX == temp) && errno == ERANGE)) {
    // Handle Error
  }

  denom = (sig_atomic_t)temp;
  signal(SIGFPE, sighandle);

  long result = 100 / (long)denom;  // Undefined Behavior
  return 0;
}

130. A signal handler called in response to SIGFPE, SIGILL, SIGSEGV, or any other implementation-defined value corresponding to a computational exception returns (7.14.2.1).

See UB Example #129.

131. A signal occurs as the result of calling the abort or raise function, and the signal handler calls the raise function (7.14.2.1).

void term_handler(int signum) {
  // SIGTERM handling specific ...
}

void int_handler(int signum) {
  // SIGINT handling specific ...
  if (raise(SIGTERM) != 0) {   // Undefined Behavior
    // Handle Error
  }
}

int main(void) {
  if (signal(SIGTERM, term_handler) == SIG_ERR) {
    // Handle Error
  }
  if (signal(SIGINT, int_handler) == SIG_ERR) {
    // Handle Error
  }

  // Program code ...

  if (raise(SIGINT) != 0) {
    // Handle Error
  }

  // More code ...

  return EXIT_SUCCESS;
}

CERT C Rule SIG30-C 3rd NCCE 12.1.5

132. A signal occurs other than as the result of calling the abort or raise function, and the signal handler refers to an object with static or thread storage duration that is not a lock-free atomic object other than by assigning a value to an object declared as volatile sig_atomic_t, or calls any function in the standard library other than the abort function, the _Exit function, the quick_exit function, the functions in <stdatomic.h> (except where explicitly stated otherwise) when the atomic arguments are lock-free, the atomic_is_lock_free function with any atomic argument, or the signal function (for the same signal number) (7.14.2.1).

#define MAX_MSG_SIZE 24
char *err_msg;

void handler(int signum) {
  if ((strcpy(err_msg, "SIGINT detected.")) == err_msg){ // Undefined Behavior
    // ...
  }
}

int main(void) {
  signal(SIGINT, handler);

  err_msg = malloc(MAX_MSG_SIZE);
  if (err_msg == NULL) {
    /// Handle Error
  }
  if ((strcpy(err_msg, "No errors yet.")) == err_msg) {
    // ...
  }

  // Main code loop ...

  return EXIT_SUCCESS;
}

133. The value of errno is referred to after a signal occurred other than as the result of calling the abort or raise function and the corresponding signal handler obtained a SIG_ERR return from a call to the signal function (7.14.2.1).

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

typedef void (*pfv)(int);

void handler(int signum) {
  pfv old_handler = signal(signum, SIG_DFL);
  if (old_handler == SIG_ERR) {
    perror("SIGINT handler"); // Undefined Behavior
    // Handle Error
  }
}
int main(void) {
  pfv old_handler = signal(SIGINT, handler);
  if (old_handler == SIG_ERR) {
    perror("SIGINT handler");
    // Handle Error
  }

  // Main code loop

  return EXIT_SUCCESS;
}

134. A signal is generated by an asynchronous signal handler (7.14.2.1).

#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>

enum { MAXLINE = 1024 };
static jmp_buf env;

void handler(int signum) {
  longjmp(env, 1);
}

void log_message(char *info1, char *info2) {
  static char *buf = NULL;
  static size_t bufsize;
  char buf0[MAXLINE];

  if (buf == NULL) {
    buf = buf0;
    bufsize = sizeof(buf0);
  }

  // Try to fit a message into buf, else reallocate
  // it on the heap and then log the message.

  // Undefined Behavior if SIGINT is raised here

  if (buf == buf0) {
    buf = NULL;
  }
}

int main(void) {
  if (signal(SIGINT, handler) == SIG_ERR) {
    // Handle Error
  }
  char *info1;
  char *info2;

  // info1 and info2 are set by user input here

  if (setjmp(env) == 0) {
    while (1) {
      // Main loop program code ...
      log_message(info1, info2);
      // More program code ...
    }
  } else {
    log_message(info1, info2);
  }
  return 0;
}

135. The signal function is used in a multi-threaded program (7.14.2.1).

#include <signal.h>
#include <stddef.h>
#include <threads.h>

volatile sig_atomic_t flag = 0;

void handler(int signum) {
  flag = 1;
}

// Runs until user sends SIGUSR1
int func(void *data) {
  while (!flag) {
    // ...
  }
  return 0;
}

int main(void) {
  signal(SIGUSR1, handler);  // Undefined Behavior
  thrd_t tid;

  if (thrd_success != thrd_create(&tid, func, NULL)) {
    // Handle Error
  }
  // ...
  return 0;
}

136. A function with a variable number of arguments attempts to access its varying arguments other than through a properly declared and initialized va_list object, or before the va_start macro is invoked (7.16, 7.16.2.2, 7.16.2.5).

#include <stdio.h>
#include <stdarg.h>

void f(int last, ...) {
  va_list args;
  int number = va_arg(args, int);  // Undefined Behavior
  va_start( args, last);           // Oops, should precede va_args!
  printf("The number is %d\n", number);
  va_end(args);
}

int main(void) {
  f(1, 2, 3);
  return 0;
}

137. The macro va_arg is invoked using the parameter ap that was passed to a function that invoked the macro va_arg with the same parameter (7.16).

#include <stdio.h>
#include <stdarg.h>

void g(va_list args) {
  int number = va_arg(args, int);
  printf("The first variadic number is %d\n", number);
}

void f(int last, ...) {
  va_list args;
  va_start( args, last);
  g(args);
  int number = va_arg(args, int);  // Undefined Behavior
  printf("The next variadic number is %d\n", number);
  va_end(args);
}

int main(void) {
  f(1, 2, 3);
  return 0;
}

138. A macro definition of va_start, va_arg, va_copy, or va_end is suppressed to access an actual function, or the program defines an external identifier with the name va_copy or va_end (7.16.2).

#include <stdarg.h>

// Undefined Behavior
#undef va_arg

int va_arg(void) {
  return 123;
}

139. The va_start or va_copy macro is invoked without a corresponding invocation of the va_end macro in the same function, or vice versa (7.16.2, 7.16.2.3, 7.16.2.4, 7.16.2.5).

#include <stdarg.h>

void f(int last, ...) {
  va_list args;
  va_start( args, last);
  // Undefined Behavior, missing va_end(args)
}

140. The va_arg macro is invoked when there is no actual next argument, or with a specified type that is not compatible with the promoted type of the actual next argument, with certain exceptions (7.16.2.2).

enum { va_eol = -1 };

unsigned int average(int first, ...) {
  unsigned int count = 0;
  unsigned int sum = 0;
  int i = first;
  va_list args;

  va_start(args, first);

  while (i != va_eol) {
    sum += i;
    count++;
    i = va_arg(args, int);
  }

  va_end(args);
  return(count ? (sum / count) : 0);
}


int avg = average(1, 4, 6, 4, 1); // Undefined Behavior

CERT C Rule MSC39-C 1st NCCE 15.6.1

141. The type parameter to the va_arg macro does not name an object type (7.16.2.2).

HYPOTHETICAL COMPILABLE EXAMPLE? (ideally one that replaces a with a function pointer example)

#include <stdio.h>
#include <stdarg.h>

int main(void) {
  int x[] = {1, 2};
  int y[] = {3, 4};
  my_printf("My data", x, y);
}

void my_printf(const char *prefix, ...) {
  va_list args;
  int *x;
  int *y;
  va_start( args, prefix);
  x = va_arg( args, int *);     // Valid
  y = va_arg( args, void());    // Undefined Behavior
  va_end( args);
  printf("%s: [%d, %d], [%d, %d]\n", prefix, x[0], x[1], y[0], y[1]);
}

142. Using a null pointer constant in form of an integer expression as an argument to a … function and then interpreting it as a void * or char * (7.16.2.2).

 #include <stdarg.h>
#include <stdio.h>

int print_string(va_list *ap) {
  char* name = va_arg(*ap, char*);  // Undefined Behavior, if value is NULL
  if (name != NULL) {
    printf("Name: %s\n", name);
  } else {
    printf("No more names\n");
  }
  return 0;
}

int print_names(int count, ...) {
  va_list ap;
  va_start(ap, count);
  for (int i = 0; i < count; i++) {
    print_string(&ap);
  }
  va_end(ap);
  print_string(NULL);               // Creates Undefined Behavior
  return 1;
}


void func() {
  print_names( 3, "Tom", "Dick", "Harry");
}

CERT C Rule MSC39-C 1st NCCE 15.6.1

143. The va_copy or va_start macro is invoked to initialize a va_list that was previously initialized by either macro without an intervening invocation of the va_end macro for the same va_list (7.16.2.3, 7.16.2.5).

#include <stdarg.h>

void f(int last, ...) {
  va_list args;
  va_start( args, last);
  va_start( args, last);   // Undefined Behavior
  va_end(args);
}

144. The va_start macro is invoked with additional arguments that include unbalanced parentheses, or unrecognized preprocessing tokens (7.16.2.5).

HYPOTHETICAL COMPILABLE EXAMPLE?

#include <stdarg.h>

  #define PAREN (

void f(int last, ...) {
  va_list args;
  va_start( args, PAREN);   // Undefined Behavior
  va_end(args);
}

145. The macro definition of a generic function is suppressed to access an actual function (7.17.1, 7.18).

#include <stdbit.h>

#undef stdc_has_single_bit
// Undefined Behavior

146. The type parameter of an offsetof macro defines a new type (7.21).

offsetof(struct S { int member; }, member);  // Undefined Behavior

147. When program execution reaches an unreachable() macro invocation (7.21.2).

if (x == 0) {
  unreachable(); // Undefined Behavior
}

148. Arbitrarily copying or changing the bytes of or copying from a non-null pointer into a nullptr_t object and then reading that object (7.21.3).

int i = 1;
int *pi = &i;
nullptr_t n = nullptr;
memcpy(&n, &pi, sizeof(n));
pi = n; // Undefined Behavior

149. The member-designator parameter of an offsetof macro is an invalid right operand of the . operator for the type parameter, or designates a bit-field (7.21).

EXTENDED COMPILABLE EXAMPLE: Consider a platform that allows invalid member-designator parameters in offset macros:

typedef struct st {
  int num1;
  int num2;
} binary_s;

size_t z = offsetof( binary_s, num3);   // Undefined Behavior, should be num2

150. The argument in an instance of one of the integer-constant macros is not a decimal, octal, or hexadecimal constant, or it has a value that exceeds the limits for the corresponding type (7.22.5).

unsigned char i = UINT8_C(0x123); // Undefined Behavior, 0x123 > 2^8

151. A byte input/output function is applied to a wide-oriented stream, or a wide character input/output function is applied to a byte-oriented stream (7.23.2).

#include <stdio.h>
#include <wchar.h>

int main(void) {
  FILE *in = fopen("foo.txt", "r");

  wchar_t wide_line[80];
  fgetws(wide_line, sizeof(wide_line) / sizeof(wchar_t), in);
  // The stream is now oriented for wide characters
  wprintf(L"The first line is: %ls", wide_line);

  char line[80];
  fgets(line, sizeof(line), in);  // Undefined Behavior
  printf("The second line is: %s", line);

  return 0;
}

152. Use is made of any portion of a file beyond the most recent wide character written to a wide-oriented stream (7.23.2).

Note: This UB has been eliminated by N3064.

153. The value of a pointer to a FILE object is used after the associated file is closed (7.23.3).

#include <stdio.h>

int close_stdout(void) {
  if (fclose(stdout) == EOF) {
    return -1;
  }

  printf("stdout successfully closed.\n");  // Undefined Behavior
  return 0;
}

154. The stream for the fflush function points to an input stream or to an update stream in which the most recent operation was input (7.23.5.2).

#include <stdio.h>

FILE *f = fopen("foo", "r");
fflush(f);  // Undefined Behavior

155. The string pointed to by the mode argument in a call to the fopen function does not exactly match one of the specified character sequences (7.23.5.3).

#include <stdio.h>

FILE *f = fopen("foo", "read");  // Undefined Behavior

156. An output operation on an update stream is followed by an input operation without an intervening call to the fflush function or a file positioning function, or an input operation on an update stream is followed by an output operation with an intervening call to a file positioning function (7.23.5.3).

void f(const char *filename, char append_data[BUFSIZ]) {
  char data[BUFSIZ];
  FILE *file;

  file = fopen(filename, "a+");
  if (file == NULL) {
    // ...
  }
  if (fwrite(append_data, sizeof(char), BUFSIZ, file) != BUFSIZ) {
    // ...
  }
  if (fread(data, sizeof(char), BUFSIZ, file) != 0) {  // Undefined Behavior
    // ...
  }
  
  fclose(file);
}

157. An attempt is made to use the contents of the array that was supplied in a call to the setvbuf function (7.23.5.6).

#include <stdio.h>

#define SIZE 1024
char buf[SIZE];
FILE *file = fopen("foo", "r");
if (file == NULL ||
    setvbuf(file, buf, _IOFBF, SIZE) != 0) {
  // Handle Error
}
// ...
putchar(buf[0]);  // Undefined Behavior

158. There are insufficient arguments for the format in a call to one of the formatted input/output functions, or an argument does not have an appropriate type (7.23.6.2, 7.23.6.3, 7.31.2.2, 7.31.2.3).

void f(void) {
  const char *error_msg = "Resource not available to user.";
  int error_type = 3;
  // ...
  printf("Error (type %s): %d\n", error_type, error_msg);  // Undefined Behavior
}

159. The format in a call to one of the formatted input/output functions or to the strftime or wcsftime function is not a valid multibyte character sequence that begins and ends in its initial shift state (7.23.6.2, 7.23.6.3, 7.29.3.6, 7.31.2.2, 7.31.2.3, 7.31.5.1).

#include <stdio.h>
#include <locale.h>

setlocale(LC_ALL, "UTF-8");

// In UTF-8: the Euro symbol == '€' == U+20AC == \xE2 \x82 \xAC == \342 \202 \254
const char s[] = {'\xE2', '\0'};  // invalid UTF-8
printf(s);   // Undefined Behavior in UTF-8 locale

160. In a call to one of the formatted output functions, a precision appears with a conversion specifier other than those described (7.23.6.2, 7.31.2.2).

#include <stdio.h>

void f(char c) {
  printf("%.3c", c);  // Undefined Behavior
}

161. A conversion specification for a formatted output function uses an asterisk to denote an argument-supplied field width or precision, but the corresponding argument is not provided (7.23.6.2, 7.31.2.2).

#include <stdio.h>

void f(int i) {
  printf("%*iX", i);  // Undefined Behavior
}

162. A conversion specification for a formatted output function uses a # or 0 flag with a conversion specifier other than those described (7.23.6.2, 7.31.2.2).

#include <stdio.h>

void f(char *s) {
  printf("%0s", s);  // Undefined Behavior
}

163. A conversion specification for one of the formatted input/output functions uses a length modifier with a conversion specifier other than those described (7.23.6.2, 7.23.6.3, 7.31.2.2, 7.31.2.3).

#include <stdio.h>

void f(int *pi) {
  printf("%lp", pi);  // 'l' not defined for %p
}

164. An s conversion specifier is encountered by one of the formatted output functions, and the argument is missing the null terminator (unless a precision is specified that does not require null termination) (7.23.6.2, 7.31.2.2).

char str[3] = "abc";  // str not null-terminated!
printf("%s\n", str);  // Undefined Behavior

165. An n conversion specification for one of the formatted input/output functions includes any flags, an assignment-suppressing character, a field width, or a precision (7.23.6.2, 7.23.6.3, 7.31.2.2, 7.31.2.3).

#include <stdio.h>

void f(int *pi) {
  printf("%-n", pi);  // Undefined Behavior
}

166. A % conversion specifier is encountered by one of the formatted input/output functions, but the complete conversion specification is not exactly %% (7.23.6.2, 7.23.6.3, 7.31.2.2, 7.31.2.3).

#include <stdio.h>

void f(void) {
  printf("%-%");  // Undefined Behavior
}

167. An invalid conversion specification is found in the format for one of the formatted input/output functions, or the strftime or wcsftime function (7.23.6.2, 7.23.6.3, 7.29.3.6, 7.31.2.2, 7.31.2.3, 7.31.5.1).

#include <stdio.h>

void f(int i) {
  printf("%q", i);  // Undefined behavior, %q not defined
}

168. The number of characters or wide characters transmitted by a formatted output function (or written to an array, or that would have been written to an array) is greater than INT_MAX (7.23.6.2, 7.31.2.2).

#include <stdio.h>
#include <limits.h>

char dest[10];

int f(int i) {
  return snprintf(dest, 10, "%*iX", INT_MAX, i);  // Undefined Behavior
}

169. The number of input items assigned by a formatted input function is greater than INT_MAX (7.23.6.3, 7.31.2.3).

#include <stdio.h>
#include <limits.h>
#include <stdarg.h>

// Assume this function is called with >INT_MAX arguments
void f(int unused, ...) {
  va_list args;
  va_start( args, unused);

  static unsigned int size = (unsigned int) INT_MAX + 1U;

  char format_string[size*2 + 1]; // will be "%c%c%c..."
  // This assumes that SIZE_MAX > 2*UINT_MAX
  for (unsigned int i = 0; i < size; i += 2) {
    format_string[i] = '%';
    format_string[i+1] = 'c';
  }
  format_string[size*2] = '\0';

  vscanf(format_string, args);    // Undefined Behavior
}

170. The result of a conversion by one of the formatted input functions cannot be represented in the corresponding object, or the receiving object does not have an appropriate type (7.23.6.3, 7.31.2.3).

long num_long;

if (scanf("%ld", &num_long) != 1) { // Undefined Behavior
  // Handle Error
}

171. A c, s, or [ conversion specifier is encountered by one of the formatted input functions, and the array pointed to by the corresponding argument is not large enough to accept the input sequence (and a null terminator if the conversion specifier is s or [) (7.23.6.3, 7.31.2.3).

char buf[BUF_LENGTH];
fscanf(stdin, "%s", buf);  // Undefined Behavior

172. A c, s, or [ conversion specifier with an l qualifier is encountered by one of the formatted input functions, but the input is not a valid multibyte character sequence that begins in the initial shift state (7.23.6.3, 7.31.2.3).

#include <stdio.h>
#include <locale.h>

setlocale(LC_ALL, "UTF-8");
// In UTF-8: the Euro symbol == '€' == U+20AC == \xE2 \x82 \xAC == \342 \202 \254
const char invalid[] = {'\xE2', '\0'};  // invalid UTF-8
wchar_t wc;
sscanf(invalid, "%lc", &wc);  // Undefined Behavior

173. The input item for a %p conversion by one of the formatted input functions is not a value converted earlier during the same program execution (7.23.6.2, 7.31.2.2).

#include <stdio.h>

char addr[] = "0x12345678"; // not produced by this code
void *ptr;
sscanf( addr, "%p", &ptr);  // Undefined Behavior

174. The vfprintf, vfscanf, vprintf, vscanf, vsnprintf, vsprintf, vsscanf, vfwprintf, vfwscanf, vswprintf, vswscanf, vwprintf, or vwscanf function is called with an improperly initialized va_list argument, or the argument is used (other than in an invocation of va_end) after the function returns (7.23.6.9, 7.23.6.10, 7.23.6.11, 7.23.6.12, 7.23.6.13, 7.23.6.14, 7.23.6.15, 7.31.2.6, 7.31.2.7, 7.31.2.8, 7.31.2.9, 7.31.2.10, 7.31.2.11).

#include <stdio.h>
#include <stdarg.h>

void f(int first, ...) {
  int local = 0;
  va_list args;
  va_start( args, local);          // Oops, not first fixed arg!
  vprintf("Hello, %s\n", args);    // Undefined Behavior
}

175. The contents of the array supplied in a call to the fgets or fgetws function are used after a read error occurred (7.23.7.2, 7.31.3.2).

const int buf_size = 100;
char buf[buf_size];
FILE *f = fopen("foo", "r");
if (f == NULL) {
  printf("Can't open foo\n");
}
fgets( buf, buf_size, f);       // Oops, no check for failure!
printf("Buf is now %s\n", buf); // Undefined Behavior if fgets() returned NULL

176. The n parameter is negative or zero for a call to fgets or fgetws. (7.23.7.2, 7.31.3.2).

char buf[10];
FILE *f = fopen("foo", "r");
if (f == NULL) {
  printf("Can't open foo\n");
}
const int buf_size = 0;       // Oops, meant to be 10!
fgets( buf, buf_size, f);     // Undefined Behavior

177. The file position indicator for a binary stream is used after a call to the ungetc function where its value was zero before the call (7.23.7.10).

FILE *f = fopen("foo", "rb");
if (f == NULL) {
  printf("Can't open foo\n");
}
assert(ftell( f) == 0);
int c = 'A';
c = ungetc( c, f);   // Undefined Behavior

178. The file position indicator for a stream is used after an error occurred during a call to the fread or fwrite function (7.23.8.1, 7.23.8.2).

FILE *f = fopen("foo", "rb");
if (f == NULL) {
  printf("Can't open foo\n");
}
const int buf_size = 125;
char buffer[buf_size];
size_t bytes = fread( buffer, 1, buf_size, f);
printf("Read %ld bytes\n", bytes);
if (bytes < buf_size) {   // uh-oh, error
  long pos = ftell( f);   // Undefined Behavior if fread() had error
  printf("File is at %lu position \n", pos);
}

179. A partial element read by a call to the fread function is used (7.23.8.1).

FILE *f = fopen("foo", "rb");
if (f == NULL) {
  printf("Can't open foo\n");
}
const int buf_size = 250;
wchar_t buffer[buf_size];
for (size_t i = 0; i < buf_size; i++) {
  buffer[i] = 0;
}
size_t bytes = fread( buffer, sizeof(wchar_t), buf_size, f);
printf("%lu bytes read\n", bytes);
if (bytes < buf_size) { // hmmm, less bytes read than expected
  printf("Possible partial byte read is %.02x, %c\n", buffer[bytes], buffer[bytes]);
  // Undefined Behavior if buffer has partially-read character
}

180. The fseek function is called for a text stream with a nonzero offset and either the offset was not returned by a previous successful call to the ftell function on a stream associated with the same file or whence is not SEEK_SET (7.23.9.2).

FILE *f = fopen("foo", "r");
if (f == NULL) {
  printf("Can't open foo\n");
}
fseek(f, 5, SEEK_CUR);  // Undefined behavior

181. The fsetpos function is called to set a position that was not returned by a previous successful call to the fgetpos function on a stream associated with the same file (7.23.9.3).

FILE *opener(const char *filename) {
  fpos_t offset;
  if (filename == NULL) {
    // ...
  }

  FILE *file = fopen(filename, "r");
  if (file == NULL) {
    // ...
  }

  memset(&offset, 0, sizeof(offset));
  if (fsetpos(file, &offset) != 0) {  // Undefined Behavior
    // ...
  }

  return file;
}

182. A non-null pointer returned by a call to the calloc, malloc, realloc, or aligned_alloc function with a zero requested size is used to access an object (7.24.4).

#include <stdlib.h>
#include <assert.h>

size_t size = 0;
int *array = malloc(size * sizeof(int));
assert(array);
array[0] = 123;  // Undefined Behavior, out-of-bounds write

183. The value of a pointer that refers to space deallocated by a call to the free or realloc function is used (7.24.4).

struct List { struct List *next; // ... };

void free_list(struct List *head) {
  for (; head != NULL; head = head->next) {  // Undefined Behavior
    free(head);
  }
}

184. The pointer argument to the free or realloc function is unequal to a null pointer and does not match a pointer earlier returned by a memory management function, or the space has been deallocated by a call to free or realloc (7.24.4.4, 7.24.4.8).

void f(size_t num_elem) {
  int error_condition = 0;
  int *x = malloc(num_elem * sizeof(int));
  if (x == NULL) {
    // ...
  }
  // ...
  if (error_condition == 1) {
    // ...
    free(x);
  }
  // ...
  free(x);   // Undefined Behavior
  x = NULL;
}

185. The value of the object allocated by the malloc function is used (7.24.4.7).

#include <stdio.h>
#include <stdlib.h>

unsigned char *p = malloc(10);
if (p != NULL) {
  printf("p[0] is %c\n", p[0]);  // Undefined Behavior
}

186. The values of any bytes in a new object allocated by the realloc function beyond the size of the old object are used (7.24.4.8).

#include <stdlib.h>
#include <stdio.h>

enum { OLD_SIZE = 10, NEW_SIZE = 20 };

int *resize_array(int *array, size_t count) {
  if (0 == count) {
    return 0;
  }

  int *ret = realloc(array, count * sizeof(int));
  if (!ret) {
    free(array);
    return 0;
  }

  return ret;
}

void func(void) {
  int *array = malloc(OLD_SIZE * sizeof(int));
  if (NULL == array) {
    // Handle Error
  }

  for (size_t i = 0; i < OLD_SIZE; ++i) {
    array[i] = i;
  }

  array = resize_array(array, NEW_SIZE);
  if (NULL == array) {
    // Handle Error
  }

  for (size_t i = 0; i < NEW_SIZE; ++i) {
    printf("%d ", array[i]);  // Undefined Behavior on new array members
  }
}

187. The program calls the exit or quick_exit function more than once, or calls both functions (7.24.5.4, 7.24.5.7).

#include <stdlib.h>

void exit1(void) {
  // Cleanup code ...
  return;
}

void exit2(void) {
  extern int some_condition;
  if (some_condition) {
    // More cleanup code ...
    exit(0);  // Undefined Behavior when invoked within exit()
  }
  return;
}

int main(void) {
  if (atexit(exit1) != 0) {
    // Handle Error
  }
  if (atexit(exit2) != 0) {
    // Handle Error
  }
  // Program code ...
  return 0;
}

188. During the call to a function registered with the atexit or at_quick_exit function, a call is made to the longjmp function that would terminate the call to the registered function (7.24.5.4, 7.24.5.7).

#include <stdlib.h>
#include <setjmp.h>

jmp_buf env;
int val;

void exit1(void) {
  longjmp(env, 1);  // Undefined Behavior
}

int main(void) {
  if (atexit(exit1) != 0) {
    // Handle Error
  }
  if (setjmp(env) == 0) {
    exit(0);
  } else {
    return 0;
  }
}

189. The string set up by the getenv or strerror function is modified by the program (7.24.5.6, 7.26.6.3).

void f3(void) {
  char *shell_dir = getenv("SHELL");

  if (shell_dir != NULL) {
    char *slash = strrchr(shell_dir, '/');
    if (slash) {
      *slash = '\0';  // Undefined Behavior
    }
    // Use shell_dir...
  }
}

190. A signal is raised while the quick_exit function is executing (7.24.5.7).

void exit_handler(void) {
  raise(SIGINT);   // Undefined Behavior
}

int main(void) {
  if (at_quick_exit(exit_handler) != 0) {
    // Handle Error
  }
  quick_exit(0);
  return 0;
}

191. A command is executed through the system function in a way that is documented as causing termination or some other form of undefined behavior (7.24.5.8).

HYPOTHETICAL COMPILABLE EXAMPLE?

int retval = system("ls /missing");   // Undefined Behavior

192. A searching or sorting utility function is called with an invalid pointer argument, even if the number of elements is zero (7.24.6).

#include <stdlib.h>

int compare(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);
}

int *arr = NULL;
qsort(arr, 0, sizeof(int), compare);  // Undefined Behavior

193. The comparison function called by a searching or sorting utility function alters the contents of the array being searched or sorted, or returns ordering values inconsistently (7.24.6).

#include <stdlib.h>

int compare(const void *a, const void *b) {
    *(int *)a = *(int *)a + 1;        // Modify array contents!
    return (*(int *)a - *(int *)b);
}

int arr[] = {2, 1, 4, 1, 5, 9, 2, 6};
size_t n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), compare);  // Undefined Behavior

194. The array being searched by the bsearch function does not have its elements in proper order (7.24.6.2).

int compare(const void *a, const void *b) {
  return *(int *)a - *(int *)b;
}

int arr[] = {2, 3, 5, 11, 7}; // Oops, mis-ordered array!
int n = sizeof(arr)/sizeof(arr[0]);
int key = 7;
int *result = (int *)bsearch(&key, arr, n, sizeof(int), compare);
// Undefined Behavior

195. The current conversion state is used by a multibyte/wide character conversion function after changing the LC_CTYPE category (7.24.8).

EXAMPLE: fscanf() is defined in C23 s7.23.6.2. Example 6, (paragraph 23) in this section describes a hypothetical encoding with shift states, which we will use for this example:

#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <memory.h>
#include <locale.h>

char str[50];
wchar_t wstr[50];
memset(wstr, 0, sizeof(wstr));
int counter = 0;
setlocale(LC_CTYPE, "HYPO");                // Use hypothetical encoding
// Suppose standard input contains the single line: ↑□X□Y↓
fgets(str, sizeof(str), stdin);             // str == "↑□X□Y↓"
counter += mbtowc(wstr, str, 4);            // wstr == "□X", upper state
setlocale(LC_CTYPE, "C");                   // state changed
counter += mbtowc(wstr, &str[counter], 4);  // Undefined Behavior

196. A string or wide string utility function is instructed to access an array beyond the end of an object (7.26.1, 7.31.4).

#include <stddef.h>
#include <string.h>

void func(void) {
  wchar_t wide_str1[] = L"0123456789";
  wchar_t wide_str2[] = L"0000000000";

  strncpy(wide_str2, wide_str1, 10);  // Undefined Behavior
}

197. A string or wide string utility function is called with an invalid pointer argument, even if the length is zero (7.26.1, 7.31.4).

#include <string.h>

char *c = 0;
int length = strlen(c); // Undefined Behavior

198. The contents of the destination array are used after a call to the strxfrm, strftime, wcsxfrm, or wcsftime function in which the specified length was too small to hold the entire null-terminated result (7.26.4.6, 7.29.3.6, 7.31.4.5.5, 7.31.5.1).

char src[] = "This is a test";
const int string_size = strlen(src) + 1;
char dest[string_size - 1];                     // Oops, too small!
int length = strxfrm(dest, src, string_size);
puts(dest);                                     // Undefined Behavior

199. A sequence of calls of the strtok function is made from different threads (7.26.5.9).

#include <string.h>
#include <threads.h>

int bar(void *) {
  char *t = strtok(NULL, "#,"); // Undefined Behavior
  return t[0];
}


char str[] = "?a???b,,,#c";
char *t = strtok(str, "?");
thrd_t thr;
if (thrd_success != thrd_create(&thr, bar, 0)) {
  // Handle Error
}

t = strtok(NULL, ","); // Undefined Behavior

int retval;
if (thrd_success != thrd_join(thr, &retval)) {
  // Handle Error
}

200. The first argument in the very first call to the strtok or wcstok is a null pointer (7.26.5.9, 7.31.4.6.8).

#include <string.h>

static char str[] = "?a???b,,,#c";
char *t;
t = strtok(NULL, "?");  // Undefined Behavior

201. A pointer returned by the strerror function is used after a subsequent call to the function, or after the calling thread has exited (7.26.6.3).

#include <stdio.h>
#include <errno.h>

char *inval = strerror(EINVAL);
char *perm = strerror(EPERM);
printf("Invalid: %s\n", inval); // Undefined Behavior, invalidated by perm
printf("Permission: %s\n", perm);

202. The type of an argument to a type-generic macro is not compatible with the type of the corresponding parameter of the selected function (7.27).

double complex dc = CMPLX( 2, 3); // 2 + 3i
double result = dsqrt(dc);        // Undefined Behavior

203. Arguments for generic parameters of a type-generic macro are such that some argument has a corresponding real type that is of standard floating type and another argument is of decimal floating type (7.27).

double d = 3.0;
_Decimal64 d64 = 2.0;
double result = remainder(d64, d);   // Undefined Behavior

204. Arguments for generic parameters of a type-generic macro are such that neither <math.h> and <complex.h> define a function whose generic parameters have the determined corresponding real type (7.27).

_Float32 f = 2.0;
long double d = 3.0;
double result = nexttoward(f, d);   // Undefined Behavior

205. A complex argument is supplied for a generic parameter of a type-generic macro that has no corresponding complex function (7.27).

float complex fc = CMPLX(2, 3); // 2 + 3i
double result = ceil(fc);       // Undefined Behavior

206. A decimal floating argument is supplied for a generic parameter of a type-generic macro that expects a complex argument (7.27).

_Decimal64 d64 = 2.0;
double result = carg(d64);   // Undefined Behavior

207. A standard floating or complex argument is supplied for a generic parameter of a type-generic macro that expects a decimal floating type argument (7.27).

double d = 3.0;
_Decimal64 d64 = 2.0;
double result = ddiv(d64, d);   // Undefined Behavior

208. A non-recursive mutex passed to mtx_lock is locked by the calling thread (7.28.4.4).

#include <threads.h>

mtx_t m;
if (thrd_success != mtx_init(&m, mtx_plain)) {
  // Handle Error
}

if (thrd_success != mtx_lock(&m)) {
  // Handle Error
}

if (thrd_success != mtx_lock(&m)) {  // Undefined Behavior
  // Handle Error
}

mtx_destroy(&m);

209. The mutex passed to mtx_timedlock does not support timeout (7.28.4.5).

#include <threads.h>
#include <time.h>

mtx_t m;
if (thrd_success != mtx_init(&m, mtx_plain)) { // Oops, should be mtx_timed!
  // Handle Error
}

struct timespec ts;
if (0 == timespec_get(&ts, TIME_UTC)) {
  // Handle Error
}
ts.tv_sec += 1; // 1 second from now

if (thrd_success != mtx_timedlock(&m, &ts)) { // Undefined Behavior
  // Handle Error
}

mtx_destroy(&m);

210. The mutex passed to mtx_unlock is not locked by the calling thread (7.28.4.7).

#include <threads.h>

mtx_t m;
if (thrd_success != mtx_init(&m, mtx_plain)) {
  // Handle Error
}

if (thrd_success != mtx_unlock(&m)) {   // Undefined Behavior
  // Handle Error
}

mtx_destroy(&m);

211. The thread passed to thrd_detach or thrd_join was previously detached or joined with another thread (7.28.5.3, 7.28.5.6).

#include <stddef.h>
#include <threads.h>

int thread_func(void *arg) {
  // Do work ...
  thrd_detach(thrd_current());
  return 0;
}

int main(void) {
  thrd_t t;

  if (thrd_success != thrd_create(&t, thread_func, NULL)) {
    // Handle Error
    return 0;
  }

  if (thrd_success != thrd_join(t, 0)) {  // Undefined Behavior
    // Handle Error
    return 0;
  }
  return 0;
}

212. The tss_create function is called from within a destructor (7.28.6.1).

#include <threads.h>

void destructor(void *arg) {
  tss_t key;
  if (thrd_success != tss_create(&key, 0)) { // Undefined Behavior
    // Handle Error
  }
}

int func(void *) {
  tss_t key;
  if (thrd_success != tss_create(&key, destructor)) {
    // Handle Error
  }
  static char str[] = "Hello";
  tss_set(key, str);
  return 0;
}

int foo(void *) {
  thrd_t thr;
  if (thrd_success != thrd_create(&thr, func, 0)) {
    // Handle Error
  }

  int retval;
  if (thrd_success != thrd_join(thr, &retval)) {
    // Handle Error
  }
  return 0;
}

213. The key passed to tss_delete, tss_get, or tss_set was not returned by a call to tss_create before the thread commenced executing destructors (7.28.6.2, 7.28.6.3, 7.28.6.4).

#include <threads.h>

int func(void *) {
  tss_t key;
  static char str[] = "Hello";
  tss_set(key, str); // Undefined Behavior, key not initialized by tss_create()
  return 0;
}

int foo(void *) {
  thrd_t thr;
  if (thrd_success != thrd_create(&thr, func, 0)) {
    // Handle Error
  }

  int retval;
  if (thrd_success != thrd_join(thr, &retval)) {
    // Handle Error
  }
  return 0;
}

214. An attempt is made to access the pointer returned by the time conversion functions after the thread that originally called the function to obtain it has exited (7.29.3).

#include <stdio.h>
#include <threads.h>
#include <time.h>

struct tm *now = NULL;

int how_soon_is_now(void *) {
  time_t n1;
  if ((time_t) -1 == time(&n1)) {
    // Handle Error
  }
  now = localtime(&n1);
  return 0;
}

int main(void) {
  thrd_t thr;
  if (thrd_success != thrd_create(&thr, how_soon_is_now, 0)) {
    // Handle Error
  }

  int retval;
  if (thrd_success != thrd_join(thr, &retval)) {
    // Handle Error
  }

  char buf[1024];
  puts("The time is: ");
  if (strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S %Z",
               now) > 0) { // Undefined Behavior
    puts(buf);
  }
  return 0;
}

215. At least one member of the broken-down time passed to asctime contains a value outside its normal range, or the calculated year exceeds four digits or is less than the year 1000 (7.29.3.2).

#include <time.h>

void func(struct tm *time_tm) {
  time_tm.tm_sec = 61;  // Invalid value, 0 <= tm_sec <= 60
  char *time = asctime(time_tm);  // Undefined Behavior
  // ...
}

216. The argument corresponding to an s specifier without an l qualifier in a call to the fwprintf function does not point to a valid multibyte character sequence that begins in the initial shift state (7.31.2.12).

#include <wchar.h>
#include <locale.h>

setlocale(LC_ALL, "UTF-8");
// In UTF-8: the Euro symbol == '€' == U+20AC == \xE2 \x82 \xAC == \342 \202 \254
const char invalid[] = {'\xE2', '\0'};   // invalid UTF-8
fwprintf(stdout, L"The string is %s\n", invalid);
// Undefined Behavior in UTF-8 locale

217. In a call to the wcstok function, the object pointed to by ptr does not have the value stored by the previous call for the same wide string (7.31.4.6.8).

#include <wchar.h>
#include <stdio.h>

wchar_t input[20] = L"foo bar baz";
wchar_t *buffer;
wchar_t *token = wcstok(input, L" ", &buffer);
while (token) {
  wprintf(L"%ls\n", token);
  wcscpy(buffer, input);             // buffer changed
  token = wcstok(0, L" ", &buffer);  // Undefined Behavior
}

218. An mbstate_t object is used inappropriately (7.31.6).

#include <string.h>
#include <wchar.h>

void func(const char *mbs) {
  size_t len;
  mbstate_t state;
  len = mbrlen(mbs, strlen(mbs), &state);  // Undefined Behavior
}

219. The value of an argument of type wint_t to a wide character classification or case mapping function is neither equal to the value of WEOF nor representable as a wchar_t (7.32.1).

#include <wctype.h>

int main(void) {
  int flag = iswalpha(WINT_MIN);
  // Undefined Behavior if sizeof(wchar_t) < sizeof(wint_t)

220. The iswctype function is called using a different LC_CTYPE category from the one in effect for the call to the wctype function that returned the description (7.32.2.3.2).

See UB 191 for background on LC_CTYPE categories. (editor; UB 191 of which version?)

#include <wchar.h>
#include <locale.h>

int f(wint_t wc) {
  wctype_t alpha = wctype("alpha");
  setlocale(LC_CTYPE, "C");     // state changed
  return iswctype(wc, alpha);   // Undefined Behavior
}

221. The towctrans function is called using a different LC_CTYPE category from the one in effect for the call to the wctrans function that returned the description (7.32.3.2.2).

#include <wchar.h>
#include <wctype.h>
#include <locale.h>

wint_t f(wint_t wc) {
  wctype_t lower = wctrans("tolower");
  setlocale(LC_CTYPE, "C");     // state changed
  return towctrans(wc, lower);  // Undefined Behavior
}

Acknowledgements

This white paper is the result of many invaluable discussions in the Undefined Behavior Study Group of ISO/IEC SC22 WG14. The following individuals, in particular, made significant contributions to this white paper: Dave Banham, Christopher Bazley, Sophie Chen, Glenn Coates, Eskil Steenberg Hald, Robert Seacord, David Svoboda, and Martin Uecker.

References

[ISO/IEC 9899:1999] ISO/IEC. Programming Languages—C, 2nd ed (ISO/IEC 9899:1999). Geneva, Switzerland: ISO, 1999.

[ISO/IEC 9899:2011] ISO/IEC. Programming Languages—C, 3rd ed (ISO/IEC 9899:2011). Geneva, Switzerland: ISO, 2011.

[ISO/IEC 9899:2024] ISO/IEC. Programming Languages—C, 5th ed (ISO/IEC 9899:2024). Geneva, Switzerland: ISO, 2024.

[n3280] ISO/IEC. The C Standard charter. June 12, 2024