Main Content

AUTOSAR C++14 Rule A18-5-8

Objects that do not outlive a function shall have automatic storage duration

Since R2021b

Description

Rule Definition

Objects that do not outlive a function shall have automatic storage duration.

Rationale

A dynamically allocated object results in additional allocation and deallocation costs and makes your program vulnerable to memory leaks if, for instance, the program returns due to an exception throw before the deallocation operation.

Instead, use an object with automatic storage duration, which has a lifetime that is bound to the enclosing scope of that object. The object is automatically destroyed when that scope exits.

The rule allows an exception for local objects that are dynamically allocated to optimize stack memory usage because the objects use a large amount of memory and might otherwise cause a stack overflow.

Polyspace Implementation

Polyspace® flags objects that are created in a function scope and that do not have automatic storage duration when any of the following is true:

  • The object is a smart pointer (std::shared_ptr or std::unique_ptr) that is never copied, moved, reassigned, reset, or passed to a callee.

    The object is not flagged if it is a non-array and, at compilation time, its size is greater than 4 KB or its size is unknown.

  • The object is dynamically allocated by using operators new or new[] and then deallocated through all possible paths within the function.

    The object is not flagged if it is a non-array and, at compilation time, its size is greater than 4 KB or its size is unknown.

  • The object is a wrapper class that contains at least one data member with a fixed size larger than 16 KB.

Troubleshooting

If you expect a rule violation but Polyspace does not report it, see Diagnose Why Coding Standard Violations Do Not Appear as Expected.

Examples

expand all

#include <iostream>
#include <cstdint>
#include <array>
#include <utility>

constexpr std::size_t size4KB = 4 * 1024;

class MyLargeBoard
{
  public:
    constexpr static size_t size16KB = 16 * 1024;
    MyLargeBoard() {}
    MyLargeBoard(int s);
  private:
    std::array<uint8_t, size16KB> cells;

};

void Reset_ptr(std::shared_ptr<std::pair<int32_t, int32_t>>& ptr)
{
    ptr.reset();
}

void Func(int32_t x_coord, int32_t y_coord,
          std::pair<int32_t, int32_t>** param_ptr)
{
    std::shared_ptr<std::pair<int32_t, int32_t>>reused_ptr(*param_ptr);

    auto toResetPtr = //Compliant, smart pointer is reset
        std::make_shared<std::pair<int32_t, int32_t>>(x_coord, y_coord);
    auto unused_ptr = //Non-compliant
        std::shared_ptr<std::pair<int32_t, int32_t>>(reused_ptr);

    if (toResetPtr->first || toResetPtr->second) {
        Reset_ptr(toResetPtr);
    }

}

void SmartPtrLargeMem()
{
    //Large non-array object
    std::shared_ptr<MyLargeBoard>
        big_non_array(new MyLargeBoard); // Compliant
    //Large array of char
    std::unique_ptr<char []>
        big_array {new char[size4KB]{'1', '2', '3', '4'}}; //Non-compliant

}

In this example, Polyspace flags these smart pointers as noncompliant:

  • Shared smart pointer unused_ptr because it is declared locally in Func and it is never copied, moved, reassigned, reset, or passed to a callee.

  • big_array which manages the dynamically allocate array of char in SmartPtrLargeMem. In this context, the use of a standard C++ container such as std::array instead of std::unique_ptr is less memory intensive.

Polyspace does not flag:

  • reused_ptr because it is used to initialize unused_ptr and toResetPtr because it is reset.

  • big_non_array because it is a non-array object with one data member of size smaller than 16 KB. The rule allows an exception for such objects because dynamic allocation can help optimize stack memory usage, for instance on an embedded device with limited memory storage.

#include <iostream>
#include <cstdint>
#include <array>
#include <utility>

constexpr std::size_t size4KB = 4 * 1024;

class MyLargeBoard
{
public:
    constexpr static size_t size16KB = 16 * 1024;
    MyLargeBoard() {}
    MyLargeBoard(int s);
private:
    std::array<uint8_t, size16KB> cells;

};


class Mytype
{
public:
    Mytype(int s = 0) : a{s} {}
private:
    int a;
};

void ReadInput(Mytype* input);
bool IsInValidRange(Mytype* input);

void* func(Mytype** output)
{
    auto input1 = new Mytype(); //Non-compliant
    ReadInput(input1);

    auto input2 = new Mytype(); //Compliant
    if (IsInValidRange(input2)) {
        delete input2;
    } else {
        *output = input2;
    }


    Mytype input3; //Compliant
    ReadInput(&input3);

    delete input1;

    return nullptr;

}

void DynamicAllocLargeMem()
{
    //Large non-array object
    MyLargeBoard* big_non_array {new MyLargeBoard}; //Compliant
    //Large array of char
    char* big_array { new char[size4KB]{'1', '2', '3', '4'}}; //Non-compliant
    // ....

    delete big_non_array;
    delete[] big_array;

}


In this example, noncompliant local variable input1 is dynamically allocated with operator new and then deallocated through all possible paths inside func. The unnecessary allocation and deallocation operations can be avoided by declaring a variable with automatic storage duration, such as input3, which is automatically deleted when func returns.

Polyspace also flags dynamically allocated array big_array. In this context, the use of a standard C++ container such as std::array is less memory intensive.

Dynamically allocated variable input2 is compliant because it is not deallocated though all possible paths inside func. The variable is escaped through output in the else branch.

Similarly, Polyspace does not flag big_non_array because it is a non-array object with one data member of size smaller than 16 KB. The rule allows an exception for such objects because dynamic allocation can help optimize stack memory usage, for instance on an embedded device with limited memory storage.

#include <iostream>
#include <cstdint>
#include <array>
#include <vector>

class MyTable
{

public:
    constexpr static size_t size64KB = 65535;
    using arrayType = std::array<uint8_t, size64KB>;
    MyTable() :   tableSize{0} {}
    MyTable(const std::string& dbPath, uint32_t inputSize) : tableSize{inputSize}
    {

        // ...
    }
    uint8_t AverageCellVal() const noexcept;
private:
    arrayType table;
    uint32_t tableSize;
};

void AvgCellVal(const std::string& dbPath, uint32_t inputSize)
{
    std::vector<uint8_t> table1(inputSize);  // Compliant
    MyTable table2(dbPath, inputSize); // Non-compliant
    uint8_t averageCellV = table2.AverageCellVal();
    std::cout << "Average cell value in " << dbPath << ": " << averageCellV << '\n';
}

class DerivedMyTable : public MyTable
{
public:
    constexpr static size_t pathMaxSize = 2 * 1024; // 2 Kb
    DerivedMyTable() : MyTable() {}
    DerivedMyTable(const std::string& dbPath, uint32_t inputSize) : MyTable(dbPath, inputSize)
    {
        std::strncpy(__dbPath, dbPath.data(), pathMaxSize - 1);
        __dbPath[pathMaxSize - 1] = '\0';
    }
private:
    char __dbPath[pathMaxSize];  // OK
};


void func(const std::string& dbPath, uint32_t inputSize)
{
    DerivedMyTable table2_derived(dbPath, inputSize);  // Non-compliant

}

In this example, base class MyTable contains a data member table of type arrayType which corresponds to an array of size 64 KB. Polyspace flags the declaration of variable table2 because it consumes a large amount of memory through MyTable and it is only used within AvgCellVal().

Instead, you can use a less memory intensive C++ container such as std::vector to declare an automatic storage duration object such as table1.

Polyspace also flags table2_derived. Even if DerivedMyTable does not contain a data member with a large size in memory, it is derived from a base class that wraps an object that consumes a large size of memory.

Check Information

Group: Language support library
Category: Required, Partially automated

Version History

Introduced in R2021b