c++gccclangmalloccompiler-optimization

Why do modern compilers assume malloc never fails?


In case of failure, malloc returns a null pointer. In the following code the latest GCC and clang assume malloc never fails and simple remove the branch

#include <cstdlib>

int main() {
    if (!malloc(1)) return 10;
}

Both new compilers generate this (with -O3). Older GCC(but not clang) is not so confident, though.

main:
        xor     eax, eax
        ret

Why is that?


Solution

  • Under the C++ standard, there is no way to access allocated memory without a pointer.

    So if you call malloc and discard the result, in the abstract machine that the C++ standard is written in terms of, there is no observable effect.

    The compiler is free to allocate resources, or to not allocate resources. If it allocates those resources (the memory space), those resources are completely unreachable. It is free to deallocate unreachable resources whenever it wants. So it could deallocate them immediately after allocating them. As it happens, not allocating resources is easier, so it doesn't bother.

    The fact that malloc has no side effects besides its return value, and the memory cannot be reached (in the abstract machine) except via the returned pointer, lets compilers optimize code that uses malloc and frees it and eliminate the memory allocation entirely.

    Like

    char const* positive_integer_as_string( int x, int base=10 ) {
      int mag = get_magnitude(x, base);
      std::unique_ptr<char, Free> pret = malloc(mag+2);\
      if (!pret)
        return nullptr;
      bool sgn = get_sign(x);
      if (sgn)
        return nullptr;
      // code to write the number
      return pret.release();
    }
    

    the compiler sees the allocation on line 3, but works out that the memory isn't touched. So it can skip the allocation entirely.

    This doesn't mean the if (!pret) branch occurs: the allocation succeeded in the abstract machine. It is just that in the compiled output there is no need to do an allocation.

    If the if (sgn) branch had logic that returned a completely different value, the compiler could even move up the free call to be earlier (to reduce memory pressure) as nobody is accessing the pointer between where it moves free and when it is actually freed, reducing memory pressure.

    As it happens, these optimizations don't even require free to occur. Allocated memory that isn't used... can be eliminated.