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.
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.
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.
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.
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.
Undefined behavior is used in many places in the C standard and for various reasons such as:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The format of this document is is:
See UB Example #4, which violates a ‘shall’ statement.
// Undefined Behavior
/* Comment with end comment marker unintentionally omitted
security_critical_function(void);#define assign(uc1, uc2, val) uc1##uc2 = val
void func(void) {
int \u0401;
// ...
assign(\,u0401, 4); // Undefined Behavior
// ...
}#include <stdio.h>
int main(float argc) { // Undefined Behavior
printf("main argument count:%f\n", argc);
return 0;
}#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
}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/* 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 */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 previousNote: 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!
}
}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
}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;
}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
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);
}
}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.// In a.c:
extern int i; // Undefined Behavior
int f(void) {
return ++i;
}
// In b.c:
short i; // Undefined Behaviorvoid *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 0void func(float f_a) {
int i_a;
i_a = f_a; // Undefined Behavior if the integral part of f_a cannot be represented.
}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
}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
}struct f *p;
void g(void) {
*p; // Undefined Behavior
}void f(void) {
/* register */ int x; // address of x not taken, so x could be stored in a register
int y = x; // Undefined Behavior
}void f(void) {
register int a[3];
int *p = a; // Undefined Behavior
a[0] = 1;
p[0];
}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);void f(void) {
char *ptr;
// ...
unsigned int number = (unsigned int)ptr; // Undefined Behavior
// ...
}void f(void) {
int *i_ptr;
char c;
i_ptr = (int *)&c; // Undefined Behavior
// ...
}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
}EXTENDED COMPILABLE EXAMPLE: Consider a platform that allows string literals to span multiple lines:
char s[] = "foo
bar"; // Undefined BehaviorEXTENDED COMPILABLE EXAMPLE: Consider a platform that allows keywords to be used as identifiers:
int if = 3; // Undefined BehaviorEXTENDED 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 BehaviorEXTENDED 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.)// 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 charactersvoid __func__(void); // Undefined Behaviorvoid f1(void) {
char *p = "string literal";
p[0] = 'S'; // Undefined Behavior
// ...
}#include "dave's_hello.h"
// Undefined Behavior#define CUBE(X) ((X) * (X) * (X))
void func(void) {
int i = 2;
int a = 81 / CUBE(++i); // Undefined Behavior
// ...
}int add(void) {
int x;
scanf("%d", &x); // Initialize x with an untrusted value, which could be INT_MAX
return x + 1; // Undefined Behavior
}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);
}
}// 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
}_Atomic struct {
int x;
} foo;
foo.x; // Undefined Behaviorchar *p = NULL;
*p; // Undefined Behaviorstruct f { struct f *x; } *p;
void g(void) {
(struct f)p; // Undefined Behavior
}int init(void) {return 0;}
int divide(int x) {
int y = init(); // 0
return x / y; // Undefined Behavior
}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);
}#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)
}#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);
}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
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
}
}
}#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 Behaviorvoid 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
}#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
struct {
int x;
int y;
} a, b;
if (&a.y < &b.y) { // Undefined Behavior
// ...
}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
}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.
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.
EXTENDED COMPILABLE EXAMPLE: Consider a platform that allows string literals to be used in constant arithmetic expressions.
float f = "Hello, world!"; // Undefined BehaviorNote: This UB has been eliminated by N3558.
const int primes[] = {2, 3, 5, 7};
const int *first_odd_prime = &(primes[1]); // Undefined BehaviorNote: This UB has been eliminated by N3244.
Note: This UB has been eliminated by N3244.
struct {
int:3; // Un-named bit-field
} myStruct; // Undefined Behavior#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');
}Note: This UB has been eliminated by N3244.
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
}#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
// ...
}
}typedef int fun_t(int);
const fun_t f; // Undefined Behaviorconst 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 *"// 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
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
// ...
}extern inline int foo(int x); // Undefined Behavior#include <stdnoreturn.h>
_Noreturn void f(void) {
return; // Undefined Behavior at run-time
}Note: This UB has been eliminated by N3244.
// In file1.c:
alignas(16) int a;
// In file2.c:
alignas(32) int a; // Undefined Behaviorint 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; // validvoid 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)
}enum { ROWS = 10, COLS = 15 };
void func(void) {
int a[ROWS][COLS];
int (*b)[ROWS] = a; // Undefined Behavior
}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);
}void f(const void);
void g(register void); // Undefined Behaviorvoid 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;
}Note: This UB has been eliminated by N3245.
Note: This UB has been eliminated by N3246.
Note: This UB has been eliminated by N3246.
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 BehaviorNote: This UB has been eliminated by N3346.
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
}extern int x;
x = 3; // Undefined Behavior if x is never defined
printf("x is %d!\n", x);// 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;
}#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");
}
}Note: This UB has been eliminated by N3347.
# 1234
// Undefined Behavior#define FOO 1
#define concat(uc1, uc2) uc1##uc2
#if concat(def, ined) FOO
// Undefined BehaviorEXTENDED 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 BehaviorEXTENDED 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'
};#include "2file.h"
// Undefined Behavior// Embed a text file and manually append a null-terminator
const char message[] = {
#embed "123message.txt" // Undefined Behavior
, '\0'
};#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
);
// ...
);#define s(x) #x
char *x = s(\); // Ill-formed, lone single quoteHYPOTHETICAL COMPILABLE EXAMPLE?
#define my_join(a, b) a ## b
char q[] = my_join(!, !); // Ill-formed#line 10000000000
// Undefined Behavior, > 2^31 - 1#pragma options align=foobar
// Undefined Behavior with GCC on Darwin platforms (e.g. Mac). See
// https://gcc.gnu.org/onlinedocs/gcc/Darwin-Pragmas.html#pragma STDC FP_CONTRACT FOOBAR
// Undefined Behavior#define __FILE__ source.c
// Undefined Behavior#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
}#include <stdio.h>
// Undefined Behavior if a non-standard stdio.h exists in include path */struct st {
int s;
#include <stdio.h>
// Undefined Behavior
};#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;
}#define sin int x;
#include <stdio.h>
// Undefined Behavior, sin() already defined#include <stddef.h>
static void *malloc(size_t nbytes); // Undefined Behavior// 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;
}#undef __STDC_UTF_32__
// Undefined Behavior#include <stdio.h>
printf("Hello, %s!\n", 123); // Undefined Behaviorvoid 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
// ...
}
}#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
}#include <assert.h>
struct {
int x;
int y;
} s;
assert(s); // Undefined Behaviorvoid f(void) {
float a = 4.0 / 7;
#pragma STDC FP_CONTRACT OFF
// Undefined Behavior
a *= 7;
printf("A is %f\n", a);
}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
}extern int errno; // Undefined BehaviorCERT C Rule MSC38-C 2nd NCCE 15.5.3
#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");
}#pragma STDC FENV_ACCESS ON
int set_excepts = fetestexcept(1); // Undefined Behavior#pragma STDC FENV_ACCESS ON
fexcept_t excepts;
fesetexceptflag(&excepts,FE_ALL_EXCEPT);
// Undefined Behavior, excepts not set by fegetexceptflag#pragma STDC FENV_ACCESS ON
fenv_t fenv;
fesetenv(&fenv);
// Undefined Behavior, fenv not initializedintmax_t x = INTMAX_MIN;
intmax_t px = imaxabs(x); // Undefined Behaviorvoid 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;
}
}
}
}#include <locale.h>
char *locale1 = setlocale(LC_ALL, "");
assert(locale1);
// ...
char *locale2 = setlocale(LC_ALL, "");
int size = strlen(locale1); // Undefined Behaviorvoid 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
}
}#include <math.h>
int math_errhandling; // Undefined Behavior#include <math.h>
#include <complex.h>
double complex p = CMPLX( 2, 3); // 2 + 3i
if (isfinite(p)) { // Undefined Behavior
// ...
}#include <setjmp.h>
int setjmp(char *foo); // Undefined Behaviorjmp_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);
}#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();
}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);
}#include <signal.h>
void *handler = NULL;
signal(SIGALRM, handler); // Undefined Behavior#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;
}See UB Example #129.
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
#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;
}#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;
}#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;
}#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;
}#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;
}#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;
}#include <stdarg.h>
// Undefined Behavior
#undef va_arg
int va_arg(void) {
return 123;
}#include <stdarg.h>
void f(int last, ...) {
va_list args;
va_start( args, last);
// Undefined Behavior, missing va_end(args)
}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 BehaviorCERT C Rule MSC39-C 1st NCCE 15.6.1
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]);
} #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
#include <stdarg.h>
void f(int last, ...) {
va_list args;
va_start( args, last);
va_start( args, last); // Undefined Behavior
va_end(args);
}HYPOTHETICAL COMPILABLE EXAMPLE?
#include <stdarg.h>
#define PAREN (
void f(int last, ...) {
va_list args;
va_start( args, PAREN); // Undefined Behavior
va_end(args);
}#include <stdbit.h>
#undef stdc_has_single_bit
// Undefined Behavioroffsetof(struct S { int member; }, member); // Undefined Behaviorif (x == 0) {
unreachable(); // Undefined Behavior
}int i = 1;
int *pi = &i;
nullptr_t n = nullptr;
memcpy(&n, &pi, sizeof(n));
pi = n; // Undefined BehaviorEXTENDED 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 num2unsigned char i = UINT8_C(0x123); // Undefined Behavior, 0x123 > 2^8#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;
}Note: This UB has been eliminated by N3064.
#include <stdio.h>
int close_stdout(void) {
if (fclose(stdout) == EOF) {
return -1;
}
printf("stdout successfully closed.\n"); // Undefined Behavior
return 0;
}#include <stdio.h>
FILE *f = fopen("foo", "r");
fflush(f); // Undefined Behavior#include <stdio.h>
FILE *f = fopen("foo", "read"); // Undefined Behaviorvoid 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);
}#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 Behaviorvoid 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
}#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#include <stdio.h>
void f(char c) {
printf("%.3c", c); // Undefined Behavior
}#include <stdio.h>
void f(int i) {
printf("%*iX", i); // Undefined Behavior
}#include <stdio.h>
void f(char *s) {
printf("%0s", s); // Undefined Behavior
}#include <stdio.h>
void f(int *pi) {
printf("%lp", pi); // 'l' not defined for %p
}char str[3] = "abc"; // str not null-terminated!
printf("%s\n", str); // Undefined Behavior#include <stdio.h>
void f(int *pi) {
printf("%-n", pi); // Undefined Behavior
}#include <stdio.h>
void f(void) {
printf("%-%"); // Undefined Behavior
}#include <stdio.h>
void f(int i) {
printf("%q", i); // Undefined behavior, %q not defined
}#include <stdio.h>
#include <limits.h>
char dest[10];
int f(int i) {
return snprintf(dest, 10, "%*iX", INT_MAX, i); // Undefined Behavior
}#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
}long num_long;
if (scanf("%ld", &num_long) != 1) { // Undefined Behavior
// Handle Error
}char buf[BUF_LENGTH];
fscanf(stdin, "%s", buf); // Undefined Behavior#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#include <stdio.h>
char addr[] = "0x12345678"; // not produced by this code
void *ptr;
sscanf( addr, "%p", &ptr); // Undefined Behavior#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
}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 NULLchar 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 BehaviorFILE *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 BehaviorFILE *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);
}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
}FILE *f = fopen("foo", "r");
if (f == NULL) {
printf("Can't open foo\n");
}
fseek(f, 5, SEEK_CUR); // Undefined behaviorFILE *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;
}#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 writestruct List { struct List *next; // ... };
void free_list(struct List *head) {
for (; head != NULL; head = head->next) { // Undefined Behavior
free(head);
}
}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;
}#include <stdio.h>
#include <stdlib.h>
unsigned char *p = malloc(10);
if (p != NULL) {
printf("p[0] is %c\n", p[0]); // Undefined Behavior
}#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
}
}#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;
}#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;
}
}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...
}
}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;
}HYPOTHETICAL COMPILABLE EXAMPLE?
int retval = system("ls /missing"); // Undefined Behavior#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#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 Behaviorint 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 BehaviorEXAMPLE: 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#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
}#include <string.h>
char *c = 0;
int length = strlen(c); // Undefined Behaviorchar 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#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
}#include <string.h>
static char str[] = "?a???b,,,#c";
char *t;
t = strtok(NULL, "?"); // Undefined Behavior#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);double complex dc = CMPLX( 2, 3); // 2 + 3i
double result = dsqrt(dc); // Undefined Behaviordouble d = 3.0;
_Decimal64 d64 = 2.0;
double result = remainder(d64, d); // Undefined Behavior_Float32 f = 2.0;
long double d = 3.0;
double result = nexttoward(f, d); // Undefined Behaviorfloat complex fc = CMPLX(2, 3); // 2 + 3i
double result = ceil(fc); // Undefined Behavior_Decimal64 d64 = 2.0;
double result = carg(d64); // Undefined Behaviordouble d = 3.0;
_Decimal64 d64 = 2.0;
double result = ddiv(d64, d); // Undefined Behavior#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);#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);#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);#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;
}#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;
}#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;
}#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;
}#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
// ...
}#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#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
}#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
}#include <wctype.h>
int main(void) {
int flag = iswalpha(WINT_MIN);
// Undefined Behavior if sizeof(wchar_t) < sizeof(wint_t)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
}#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
}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.
[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