Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Detecting memory management bugs with GCC 11, Part 2: Deallocation functions

May 5, 2021
Martin Sebor
Related topics:
C, C#, C++LinuxOpen sourceSecurity
Related products:
Red Hat Enterprise Linux

Share:

    The first half of this article described dynamic memory allocation in C and C++, along with some of the new GNU Compiler Collection (GCC) 11 features that help you detect errors in dynamic allocation. This second half completes the tour of GCC 11 features in this area and explains where the detection mechanism might report false positives or false negatives.

    Throughout this article, I include links to the code examples on Compiler Explorer for those who would like to experiment. You will find the links above the source code of each example.

    Note: Read the first half of this article: Detecting memory management bugs with GCC 11, Part 1: Understanding dynamic allocation.

    Mismatches between new and delete operators

    A peculiarity of C++ is that calls to a specific form and overload of operator new() must be paired with the corresponding form and overload of operator delete(). C++ provides many forms of these operators out of the box, and GCC knows about all those. But besides these, C++ programs can also define their own overloads of these operators. Those that do must make sure to define them in matching pairs. Otherwise, attempts to deallocate objects allocated by the wrong form can easily lead to the same insidious bugs as other mismatches. In addition, a class that declares the scalar forms of the operators should also declare a pair of the corresponding array forms. (See also R.15: Always overload matched allocation/deallocation pairs in the C++ Core Guidelines.)

    For example, suppose we define a class that manages its own memory. We define member forms of operator new() and operator delete(), but neglect to define the array forms of the operators. Next, we allocate an array of objects of the class that we then try to deallocate, as shown in the following snippet. GCC 11 detects this mistake and issues a -Wmismatched-new-delete warning pointing it out. Because operator new() and operator delete() are recognized as special, this happens even without attribute malloc. The array_new_delete test case shows an example:

    #include <stddef.h>
    
    struct A
    {
      void* operator new (size_t);
      void operator delete (void*);
    };
    
    void f (A *p)
    {
      delete p;
    }
    
    void test_array_new_delete (void)
    {
      A *p = new A[2];
      f (p + 1);
    }
    

    The compiler warning is:

    In function 'void f(A*)',
        inlined from 'void test_array_new_delete()':
    warning: 'static void A::operator delete(void*)' called on pointer returned from a mismatched allocation function [-Wmismatched-new-delete]
    11 | delete p;
       | ^
    In function 'void test_array_new_delete()':
    note: returned from 'void* operator new [](long unsigned int)'
    16 | A *p = new A[2];
       |        ^
    

    Notice how the example calls f() with a pointer that doesn't point to the beginning of the allocated array, and the warning is still able to detect that it's not a valid argument to the deallocation function. Even though the warning is in effect even without optimization, the example must be compiled with the -O2 option in order for GCC to find the bug. That's because the invocations of the new and delete expressions are in different functions that GCC must inline in order to detect the mismatch.

    Deallocating an unallocated object

    Another dynamic memory management bug is attempting to deallocate an object that wasn't dynamically allocated. For example, it's a bug to pass the address of a named object such as a local variable to a deallocation function. Usually, the deallocation call will crash, but it doesn't have to do that. What happens tends to depend on the contents of the memory that the object points to. GCC has long implemented a warning to detect these kinds of bugs: -Wfree-nonheap-object. But until GCC 11, the option detected only the most obvious bugs involving the free() function—basically just passing the address of a named variable to it. GCC 11 has been enhanced to check every call to every known C or C++ deallocation function. In addition to free(), these functions include realloc() and, in C++, all non-placement forms of operator delete(). In addition, calls to user-defined deallocation functions marked up with attribute malloc are checked. As an example, see free_declared:

    #include <stdlib.h>
    
    void use_it (void*);
    
    void test_free_declared (void)
    {
      char buf[32], *p = buf;
      use_it (p);
      free (p);
    }
    

    The compiler warning is:

    In function 'test_free_declared':
    warning: 'free' called on unallocated object 'buf' [-Wfree-nonheap-object]
    9 | free (p);
      | ^~~~~~~~
    note: declared here
    7 | char buf[32], *p = buf;
      |      ^~~
    

    Attribute alloc_size

    Besides the two forms of attribute malloc, one other attribute helps GCC find memory management bugs. The attribute alloc_size tells GCC which of an allocation function's arguments specify the size of the allocated object. For instance, the malloc() and calloc() functions are implicitly declared like so:

    __attribute__ ((malloc, malloc (free, 1), alloc_size (1)))
    void* malloc (size_t);
    
    __attribute__ ((malloc, malloc (free, 1), alloc_size (1, 2)))
    void* calloc (size_t, size_t);
    

    Making use of attribute alloc_size helps GCC find out-of-bounds accesses to allocated memory. The my_alloc_free example shows how to use the attribute with a user-defined allocator:

    void my_free (void*);
    
    __attribute__ ((malloc, malloc (my_free, 1), alloc_size (1)))
    void* my_alloc (int);
    
    void* f (void)
    {
      int *p = (int*)my_alloc (8);
      memset (p, 0, 8 * sizeof *p);
      return p;
    }
    

    The compiler warning is:

    In function 'test_memset_overflow':
    warning: 'memset forming offset [8, 31] is out of the bounds [0, 8] [-Warray-bounds]
    9 | memset (p, 0, 8 * sizeof *p);
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    Limitations

    Being new, GCC 11's detection of dynamic memory management bugs isn't perfect. The warnings are susceptible to both false positives and false negatives.

    False positives

    False positives are typically caused by GCC failing to determine that certain code paths are unreachable. This tends to affect especially -Wfree-nonheap-object in code that uses either a declared array or a dynamically allocated buffer depending on some condition, and then deallocates the buffer based on some other but equivalent condition. When GCC cannot prove the two conditions are equivalent, it might issue the warning. GCC bug 54202 shows how this might happen. It's worth noting that the bug was submitted in 2012 against GCC 4.7. So the warning is quite old, but the original implementation detected only the most basic kinds of bugs, so false positives were rare. But because GCC 11 has enhanced the implementation of the warning to check every call to every known deallocation function, these sorts of false positives are going to come up more frequently, in proportion to the number of bugs found. The test case from bug 54202 can be seen on Compiler Explorer:

    typedef struct Data
    {
      int refcount;
    } Data;
    
    extern const Data shared_null;
    
    Data *allocate()
    {
      return (Data *)(&shared_null);
    }
    
    void dispose (Data *d)
    {
      if (d->refcount == 0)
        free (d);
    }
    
    void f (void)
    {
      Data *d = allocate();
      dispose (d);
    }
    

    The compiler warning is:

    In function 'dispose',
        inlined from 'f':
    warning: attempt to free a non-heap object 'shared_null' [-Wfree-nonheap-object]
    18 | free(d)
       | ^~~~~~~
    

    We do hope these cases won't be too common, but plan to reduce them even further in future updates. Until then, when they happen, we recommend using #pragma GCC diagnostic to disable the warning.

    False negatives

    Similarly, but to a greater extent, buggy code that conditionally tries to deallocate an unallocated object may not be diagnosed at all. These false negatives are quite common and unavoidable in general, due to the limitations of the analysis in GCC. One major reason is that the accuracy and depth of the analysis depend on optimization in general and on inlining in particular. Besides being enabled only with optimization, inlining is subject to constraints designed to strike an optimal balance between speed and space efficiency. Whether a call to a particular function is inlined into its caller depends on its benefits to the caller. Internally, the profitability is determined by the size of the function in GCC pseudo-instructions. This constraint is controlled by the -finline-limit= option. Setting the option to a suitably high value has the effect of inlining most functions defined in a translation unit and exposing their bodies to the analysis. (Changing the limit is not recommended for code that is to be released.) In addition, Link-Time Optimization (LTO), enabled by the -flto option, applies the same analysis to functions inlined across translation unit boundaries.

    That being said, we are aware of a class of false negatives that aren't subject to inlining heuristics and where it's possible to do better. We are hoping to tackle some of those in future releases. An example of a limitation that's solvable, albeit not easily, is in the code that follows. Because the call to f() might overwrite the value that g() stores in *p, issuing a warning would be a false positive in those cases. If f() doesn't modify the object passed to it, declaring the function to take a const void* argument instead might seem like a solution. But because it isn't an error to cast constness away, GCC must conservatively assume that the function might, in fact, do so. This assumption is necessary in order to emit correct code, but warnings can reasonably make stronger assumptions, albeit at the cost of some false positives for strictly correct (though clearly questionable) programs. Implementing these stricter assumptions is among the enhancements considered for future releases.

    void f (void*);
    
    void g (int n)
    {
      char a[8];
      char *p = 8 < n ? malloc (n) : a;
      *p = n;
      f (p + 1);    // might change *p
      if (*p < 8)
        free (p);   // missing warning
    }

    Conclusion to Part 2

    GCC 11 can find many memory management bugs out of the box, without making any changes to program source code. But to get the most out of these features in code that defines its own memory management routines, you can benefit by annotating these functions with the attributes malloc and alloc_size. GCC checks all calls to memory allocation and deallocation functions even without optimization, but detection in the absence of optimization is limited to the scope of function bodies. With optimization, the analysis also includes functions inlined into their callers. Raising the inlining limit improves the analysis, as does LTO. The GCC static analyzer performs the same checking but considers all functions in the same translation unit, regardless of inlining.

    Last updated: February 11, 2024

    Recent Posts

    • Assessing AI for OpenShift operations: Advanced configurations

    • OpenShift Lightspeed: Assessing AI for OpenShift operations

    • OpenShift Data Foundation and HashiCorp Vault securing data

    • Axolotl meets LLM Compressor: Fast, sparse, open

    • What’s new for developers in Red Hat OpenShift 4.19

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue