Fill Out All The Margins đź“–: OpenSSF Releases Compiler Annotations Guide for C and C++

By February 12, 2026Blog, Guest Blog

By Thomas Nyman, Ericsson; David A. Wheeler, Linux Foundation; Siddhesh Poyarekar, Red Hat

Modern compilers offer powerful annotation features that can dramatically improve memory safety, correctness, diagnostics, and even performance, but they’re often underused or misunderstood. OpenSSF’s new Compiler Annotations for C and C++ guide helps developers use compiler-specific annotations to communicate code intent to the compiler, improve diagnostics, improve optimizations, and provide stronger security and correctness guarantees. This new guide documents the behavior, syntax, and portability of annotations across compilers. It currently focuses on GCC and Clang/LLVM and explains how annotations can help prevent subtle bugs, enable better static analysis, and improve run-time security. The guide provides a practical, developer-focused overview of compiler annotations in C and C++, explaining what they do, when to use them, and how to apply them effectively in real-world code.

Revisiting the C and C++ Memory-Safety Hardening Challenge

C and C++ remain dominant in systems, embedded, and performance-critical software, but their low-level design and lack of on-by-default safety mechanisms make them especially prone to memory-safety issues such as buffer overflows and memory corruption. These flaws continue to account for a large share of exploitable vulnerabilities, a concern repeatedly highlighted by major cybersecurity authorities. Industry reports from Microsoft and Google Chromium indicate that roughly 70% of serious vulnerabilities in their products stem from memory-safety issues, because such errors are easy to make in C and C++.

Mitigating these risks at scale is difficult. Rewriting the vast existing C/C++ codebase is impractical; one estimate places the cost at roughly $2.4 trillion. Even a gradual shift to safer languages does not fully eliminate the problem, as many modern ecosystems still depend on C/C++ code or unsafe extensions. As a result, strengthening existing C and C++ software remains essential.

One practical and immediate mitigation is compiler-based hardening. Appropriate compiler options can substantially improve resilience against memory-safety and related flaws, but selecting and configuring them correctly is nontrivial. The OpenSSF Compiler Options Hardening Guide for C and C++ addresses this gap by providing developers with clear guidance on how to use compiler features to improve the security of C and C++ software. These hardening options, however, are primarily stopgap measures and mitigations that disarm dormant memory-safety flaws from becoming exploitable vulnerabilities or reduce their impact. All developers should strive to eliminate memory-safety issues at the source, by addressing the software defects that are the root causes of memory-safety issues. This is unachievable by manual effort alone; a modern secure software development lifecycle for C and C++ code leverages both static and dynamic analysis to identify memory-safety issues and other defects. 

Enter static analysis!

Static analysis is the process of analyzing code without executing it. Compilers perform static analysis; there are also specialized static analysis tools that look for defects such as vulnerabilities. Static analysis tools (both compilers and specialized tools) are particularly useful when “shifting security left”; that is, conducting security-relevant testing earlier in the software development lifecycle. Since static analysis can analyze source code for problems without running the program, it can provide early warnings of potential issues across all program paths. Security-conscious development practices “gate” proposed code changes through static analysis until the findings can be either fixed or ruled out as false positives.

Static analysis is not a panacea: automated analysis of arbitrary C and C++ code cannot be guaranteed to be sound, which means it can report false positives, nor complete, which means it cannot identify all erroneous cases. This is because reasoning about whether a piece of C and C++ behaves correctly in all cases requires understanding all the contexts in which it can be used. This relies on the compiler being able to see the flow of data between different points in a program, across functions and modules. This is quite a challenge in C and C++ because both languages allow passing around opaque references, thus losing information about objects.

Compilers don’t understand comments…

Modern programming languages are increasingly designed to support abstract local reasoning; the ability to look at a defined unit of code, a function, or a class, understand it, and verify its correctness without understanding all the contexts within which it is used. C and C++ can be written in ways which makes it more amenable to local reasoning, such as by following the C++ Core Guidelines. However, compilers and separate static analysis tools cannot check for rules (or conventions) that are not expressed in code. That’s true even if those conventions are documented by the developers, e.g., in comments.

For example, it’s fairly common for embedded projects or performance-critical software to establish their own conventions for memory allocations (such as implementing a custom allocator). While static analysis tools can detect when code violates conventional malloc() semantics, such as when an allocation lacks a free(), they cannot recognize that pointers returned by a custom allocation should follow the same rules.

AI tools can examine documentation (including comments) and sometimes they can find vulnerabilities. However, their probabilistic nature and the complexity of modern systems means that AI tools often will miss important vulnerabilities unless they have additional trustworthy information.

…but they can read attributes!

Modern open source toolchains (including compilers) provide built-in static analysis that goes far beyond conventional warning diagnostics: GCC Static Analyzer currently supports C code, while the Clang Static Analyzer can find bugs in C, C++, and Objective-C programs. What sets these built-in static analyzers apart from other static analysis tools is that they can leverage additional semantic information provided to the compiler through the use of function or variable attributes (such as those provided through __attribute__ ((…)) or the  C++11/C23 [[prefix::attribute]] annotations). These allow developers to provide the compiler with additional semantics beyond those expressible with traditional standard C and C++ syntax.

For example, developers can tell compilers that a function returns pointers that are expected to be handled similarly to pointers returned by malloc() by annotating that function with the [[gnu::malloc]] annotation. They can even declare the corresponding deallocator (using [[gnu::malloc(my_free]] in GCC or [[clang::ownership_takes(my_allocation, 1)]] in Clang/LLVM.

A particularly important annotation is counted_by(variable). Buffer overflows are a common type of vulnerability in these languages because arrays can be passed as pointers without the compiler knowing how many elements are in the array, even though this information is often available. This annotation tells the compiler what variable contains the element count, so the compiler and other tools are more likely to detect vulnerabilities.

When a compiler’s static analysis can’t determine if some construct is vulnerable or not, that doesn’t need to be the end of the story. Compilers can generate code to perform run-time checks when they cannot determine whether a construct is safe. For instance, better information about the correct bounds of in-memory objects, such as that provided by counted_by(), allows the compiler to effectively interact with libc hardening features, such as _FORTIFY_SOURCE=3, which allows compile-time replacement of calls to functions which might potentially overflow a buffer to be replaced with safe, bounds-checked versions automatically. What’s more, compilers can generate such checked code only when it’s necessary, reducing the overhead of such checks.

OpenSSF’s Compiler Annotations Guide

OpenSSF’s Compiler Annotations Guide is a practical reference for understanding and applying security-relevant compiler-supported annotations to express the intent of code directly to the compiler. It documents what these annotations do, how they behave in practice, and how they differ across toolchains, with an initial focus on GCC and Clang/LLVM. Rather than cataloging features in isolation, the guide explains when and why to use annotations, illustrates common patterns, and highlights portability considerations so developers can apply them safely in real-world codebases.

Why This Guide Matters

Why this matters is simple: compilers and static analyzers can only reason about what they can see. In C and C++, critical semantic information—such as ownership, allocation behavior, length, nullability, or required calling conventions—is often implicit or described only in comments. Annotations provide a way to make those assumptions explicit and machine-checkable. By doing so, they enable more precise diagnostics, reduce false positives in static analysis, and help catch subtle bugs that would otherwise slip through code review and testing.

Used effectively, compiler annotations improve the signal-to-noise ratio of analysis tools, and support a more robust secure development lifecycle for existing C and C++ code. They are not a silver bullet, but they are a low-friction, immediately deployable technique for improving correctness and security at scale, especially in large, long-lived codebases where rewriting or wholesale architectural changes are not an option.

How to get involved

If you are developing software in C or C++, we encourage you to explore the guide available here:
https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Annotations-for-C-and-C++.html

If you are interested in contributing, you can join the Best Practices for Open Source Developers Working Group community on GitHub and on the OpenSSF Slack channel. 

About the Authors

Thomas Nyman leads the C/C++ Compiler Hardening Guide initiative under the OpenSSF Best Practices Working Group. He is an Expert in Trusted Hardware and Software Technologies at Ericsson where he works with platform security for critical telecommunications infrastructure. He holds a PhD in Computer Science from Aalto University, Finland, where he worked on systems security research in the areas of mobile and embedded platform security.

David A. Wheeler is the Director of Open Source Supply Chain Security at the Linux Foundation. Dr. Wheeler has a PhD in Information Technology, a Master’s in Computer Science, a certificate in Information Security, a certificate in Software Engineering, and a B.S. in Electronics Engineering. He is a Certified Information Systems Security Professional (CISSP) and Senior Member of the IEEE. He lives in Northern Virginia.

Siddhesh Poyarekar is the Tech Lead for the GCC team at Red Hat. He is one of the lead maintainers of the GNU C Library and a contributor to GCC.