exponenta event banner

CERT C++: EXP54-CPP

Не получать доступ к объекту вне срока его службы

Описание

Определение правила

Не получать доступ к объекту вне срока его службы. [1 ]

Внедрение Polyspace

Эта проверка проверяет наличие следующих проблем:

  • Неинициализированный указатель.

  • Неинициализированная переменная.

  • Использование ранее освобожденного указателя.

  • Указатель или ссылка на переменную стека, покидающую область.

  • Доступ к объекту с временным временем жизни.

Примеры

развернуть все

Проблема

Неинициализированный указатель возникает, когда указателю не назначен адрес до отмены привязки.

Риск

Если указателю явно не назначен адрес, он указывает на непредсказуемое расположение.

Зафиксировать

Исправление зависит от первопричины дефекта. Например, вы назначили адрес указателю, но назначение недоступно.

Часто детали результата показывают последовательность событий, которые привели к дефекту. Исправление может быть реализовано для любого события в последовательности. Если сведения о результатах не отображают историю событий, можно выполнить обратную трассировку, щелкнув правой кнопкой мыши параметры в исходном коде и просмотреть предыдущие связанные события. См. также раздел Интерпретация результатов поиска ошибок в интерфейсе пользователя Polyspace Desktop.

См. примеры исправлений ниже. Рекомендуется инициализировать указатель на значение NULL при объявлении указателя.

Если вы не хотите устранять проблему, добавьте комментарии к результату или коду, чтобы избежать другой проверки. См. раздел Результаты анализа пространства адресов с помощью исправлений ошибок или обоснований.

Пример - Неинициализированная ошибка указателя
#include <stdlib.h>

int* assign_pointer(int* prev)
{
    int j = 42;
    int* pi;

    if (prev == nullptr) 
      {
        pi = new int;
        if (pi == nullptr) return NULL;
      }
    *pi = j;                    
    /* Defect: Writing to uninitialized pointer */

    return pi;
}

Если prev не является nullptr, указатель pi не назначен адрес. Однако pi обнуляется на всех путях выполнения, независимо от того, prev является nullptr или нет.

Исправление - инициализация указателя на каждом пути выполнения

Одной из возможных корректировок является присвоение адреса pi когда prev не является nullptr. Кроме того, можно инициализировать pi в качестве nullptr во время его декларирования.

#include <cstdlib>

int* assign_pointer(int* prev)
{
    int j = 42;
/*Fix: Initialize pointers by using nullptr during declaration*/
    int* pi = nullptr;

    if (prev == NULL) 
       {
        pi = new int;
        if (pi == nullptr) return NULL;
       } 
    /* Fix: Initialize pi in branches of if statement  */
    else 
        pi = prev;              
    *pi = j;
    return pi;
}
Проблема

Неинициализированная переменная возникает, когда переменная не инициализируется до считывания ее значения.

Риск

Если переменная не инициализирована явным образом, значение переменной непредсказуемо. Нельзя полагаться на переменную, имеющую определенное значение.

Зафиксировать

Исправление зависит от первопричины дефекта. Например, вы назначили значение переменной, но назначение недоступно, или вы назначили значение переменной в одной из двух ветвей условного оператора. Исправьте недоступный код или отсутствующее назначение.

Часто детали результата показывают последовательность событий, которые привели к дефекту. Исправление может быть реализовано для любого события в последовательности. Если сведения о результатах не отображают историю событий, можно выполнить обратную трассировку, щелкнув правой кнопкой мыши параметры в исходном коде и просмотреть предыдущие связанные события. См. также раздел Интерпретация результатов поиска ошибок в интерфейсе пользователя Polyspace Desktop.

См. примеры исправлений ниже. Рекомендуется инициализировать переменную при объявлении.

Если вы не хотите устранять проблему, добавьте комментарии к результату или коду, чтобы избежать другой проверки. См. раздел Результаты анализа пространства адресов с помощью исправлений ошибок или обоснований.

Пример - Ошибка неинициализированной переменной
int get_sensor_value(void)
{
    extern int getsensor(void);
    int command;
    int val;

    command = getsensor();
    if (command == 2) 
      {
        val = getsensor();
      }

    return val;              
    /* Defect: val does not have a value if command is not 2 */
}

Если command не равно 2, переменная val не назначен. В этом случае возвращаемое значение функции get_sensor_value не определен.

Исправление - инициализация во время объявления

Одной из возможных корректировок является инициализация val во время объявления, чтобы инициализация не обходилась по некоторым путям выполнения.

int get_sensor_value(void)
{
    extern int getsensor(void);
    int command;
    /* Fix: Initialize val */
    int val=0;

    command = getsensor();
    if (command == 2) 
      {
        val = getsensor();
      }

    return val;              
 }

val присваивается начальное значение 0. Когда command не равно 2, функция get_sensor_value возвращает это значение.

Проблема

Использование ранее освобожденного указателя происходит при доступе к блоку памяти после освобождения блока, например, с помощью free функции или delete оператор.

Риск

Когда указателю назначается динамическая память с помощью функций malloc, calloc, realloc или оператор newуказывает на расположение памяти в куче. При использовании free функции или delete для этого указателя соответствующий блок памяти освобождается. Попытка доступа к этому блоку памяти может привести к непредсказуемому поведению или даже к сбою сегментации.

Зафиксировать

Исправление зависит от первопричины дефекта. Убедитесь, что вы намерены освободить память позже или выделить другой блок памяти указателю перед доступом.

Рекомендуется после освобождения блока памяти назначить соответствующий указатель nullptr. Перед удалением ссылок проверьте, есть ли они nullptr и обработайте ошибку. Таким образом, вы защищены от доступа к освобожденному блоку.

Пример - Использование ранее освобожденной ошибки указателя
#include <cstdlib>
int increment_content_of_address(int base_val, int shift)
   { 
    int j;
    int* pi = new int;
    if (pi == NULL) return 0;

    *pi = base_val;
    delete pi;

    j = *pi + shift;
    /* Defect: Reading a deallocated pointer */
 
    return j;
   }

delete оператор освобождает блок памяти, который pi относится к. Поэтому обособлениеpi после delete pi; недопустимый оператор.

Исправление - удаление указателя после использования

Одной из возможных корректировок является освобождение указателя pi только после последнего экземпляра, к которому осуществляется доступ.

#include <cstdlib>

int increment_content_of_address(int base_val, int shift)
{
    int j;
    int* pi = new int;
    if (pi == NULL) return 0;

    *pi = base_val;

    j = *pi + shift;
    *pi = 0;

    /* Fix: The pointer is deallocated after its last use */
    delete pi;               
    return j;
}
Коррекция - использование std::unique_ptr

Другой возможной поправкой является использование std::unique_ptr вместо необработанного указателя. Интеллектуальные указатели, такие как std::unique_ptr управляет собственными ресурсами. так как нет необходимости удалять смарт-указатели явным образом, доступ к ним после отмены назначения осуществляется непреднамеренно.

#include <cstdlib>
#include <memory>

int increment_content_of_address(int base_val, int shift)
{
    int j;
    /* Fix: A smart pointer is used*/
    std::unique_ptr<int>   pi(new int(3));
    if (pi == nullptr) return 0;

    *pi = base_val;

    j = *pi + shift;
    *pi = 0;
    return j;
}
Проблема

Указатель или ссылка на переменную стека, покидающую область, возникает, когда указатель или ссылка на локальную переменную выходит из области действия переменной. Например:

  • Функция возвращает указатель на локальную переменную.

  • Назначение выполняется функцией globPtr = &locVar. globPtr является глобальной переменной указателя и locVar является локальной переменной.

  • Назначение выполняется функцией *paramPtr = &locVar. paramPtr является параметром функции, который является, например, int** указатель и locVar является локальным int переменная.

  • Метод C++ выполняет назначение memPtr = &locVar. memPtr является элементом данных указателя класса, которому принадлежит метод. locVar является переменной, локальной для метода.

Дефект также относится к памяти, выделенной с помощью alloca функция. Дефект не относится к статическим локальным переменным.

Риск

Локальным переменным назначается адрес в стеке. После окончания области действия локальной переменной этот адрес становится доступным для повторного использования. Использование этого адреса для доступа к значению локальной переменной вне области действия переменной может привести к непредвиденному поведению.

Если указатель на локальную переменную выходит из области действия переменной, Polyspace ® Bug Finder™ выделяет дефект. Дефект появляется, даже если адрес, сохраненный в указателе, не используется. Для поддерживаемого кода рекомендуется не разрешать указателю выходить из области действия переменной. Даже если вы сейчас не используете адрес в указателе, кто-то другой, использующий вашу функцию, может использовать адрес, вызывая неопределенное поведение.

Зафиксировать

Не допускайте выхода указателя или ссылки на локальную переменную из области действия переменной.

Пример - Указатель на локальную переменную, возвращаемую функцией
void func2(int *ptr) {
    *ptr = 0;
}

int* func1(void) {
    int ret = 0;
    return &ret ;
}
void main(void) {
    int* ptr = func1() ;
    func2(ptr) ;
}

В этом примере: func1 возвращает указатель на локальную переменную ret.

В main, ptr указывает на адрес локальной переменной. Когда ptr доступен в func2, доступ запрещен, поскольку область действия ret ограничивается func1,

Пример - Указатель на локальную переменную через лямбда-выражение
auto createAdder(int amountToAdd) {
  int addThis = amountToAdd;
  auto adder = [&] (int initialAmount) {
      return (initialAmount + addThis);
  };
  return adder;
}
 
void func() {
  auto AddByTwo = createAdder(2);
  int res = AddByTwo(10);
}

В этом примере createAdder функция определяет лямбда-выражение adder который захватывает локальную переменную addThis по ссылке. Объем addThis ограничивается createAdder функция. Когда объект возвращен createAdder вызывается, ссылка на переменную addThis доступен за пределами его области действия. При обращении таким образом значение addThis не определен.

Исправление - захват локальных переменных копированием в лямбда-выражении вместо ссылки

Если функция возвращает объект лямбда-выражения, избегайте захвата локальных переменных по ссылке в лямбда-объекте. Захватите переменные путем копирования.

Переменные, захваченные копией, имеют тот же срок службы, что и лямбда-объект, но переменные, захваченные ссылкой, часто имеют меньший срок службы, чем сам лямбда-объект. При использовании лямбда-объекта эти переменные, доступные вне области действия, имеют неопределенные значения.

auto createAdder(int amountToAdd) {
  int addThis = amountToAdd;
  auto adder = [=] (int initialAmount) {
      return (initialAmount + addThis);
  };
  return adder;
}
 
void func() {
  auto AddByTwo = createAdder(2);
  int res = AddByTwo(10);
}
Проблема

Доступ к объекту с временным временем жизни происходит при попытке чтения или записи в объект с временным временем жизни, возвращаемым вызовом функции. В структуре или объединении, возвращаемых функцией и содержащих массив, элементы массива являются временными объектами. Время жизни временных объектов заканчивается:

  • Когда полное выражение или полный декларатор, содержащий вызов, заканчивается, как определено в C11 Standard.

  • После следующей точки последовательности, как определено в стандартах C90 и C99. Точка последовательности - это точка в выполнении программы, в которой все предыдущие оценки завершены и последующая оценка еще не начата.

Для кода C++ при доступе к объекту с временным временем жизни возникает дефект только при записи в объект с временным временем жизни.

Если временный объект времени жизни возвращается по адресу, дефекты не возникают.

Риск

Изменение объектов с временным временем жизни является неопределенным поведением и может привести к ненормальному завершению программы и проблемам с переносимостью.

Зафиксировать

Присвойте объект, возвращенный вызовом функции, локальной переменной. Содержимое временного объекта жизненного цикла копируется в переменную. Теперь его можно изменить безопасно.

Пример - Изменение временного объекта времени жизни, возвращаемого вызовом функции
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define SIZE6 6

struct S_Array
{
    int t;
    int a[SIZE6];
};

struct S_Array func_temp(void);

/* func_temp() returns a struct value containing
* an array with a temporary lifetime.
*/
int func(void) {
 
/*Writing to temporary lifetime object is
 undefined behavior
 */
    return ++(func_temp().a[0]); 
}

void main(void) {
    (void)func();
}

В этом примере: func_temp() возвращает по значению структуру с элементом массива a. Этот член имеет временный срок службы. Приращение является неопределенным поведением.

Исправление - назначение возвращаемого значения локальной переменной перед записью

Одной из возможных корректировок является назначение возврата вызова func_temp() локальной переменной. Содержимое временного объекта a копируется в переменную, которую можно безопасно увеличить.

 #include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define SIZE6 6

struct S_Array
{
    int t;
    int a[SIZE6];
};

struct S_Array func_temp(void);

int func(void) {

/* Assign object returned by function call to 
 *local variable
 */
    struct S_Array s = func_temp(); 

/* Local variable can safely be
 *incremented
 */
    ++(s.a[0]);                                           
    return s.a[0];
}

void main(void) {
    (void)func();
}

Проверить информацию

Группа: 02. Выражения (EXP)
Представлен в R2019a

[1] Данное программное обеспечение было создано компанией MathWorks и включает в себя следующие компоненты: «Веб-сайт SEI CERT-C», © 2017 Университет Карнеги-Меллон, веб-сайт SEI CERT-C + + © 2017 Университет Карнеги-Меллон, "Стандарт кодирования SEI CERT C - Правила разработки безопасных, Надежные и безопасные системы - 2016 Edition ", © 2016 Университет Карнеги-Меллон, и "Стандарт кодирования SEI CERT C++ - Правила разработки безопасных, Надежные и безопасные системы в C++ - 2016 Edition "© 2016 Университет Карнеги-Меллон, со специальным разрешением от его Института программного обеспечения.

ЛЮБОЙ МАТЕРИАЛ УНИВЕРСИТЕТА КАРНЕГИ МЕЛЛОНА И/ИЛИ ЕГО ПРОГРАММНОГО ИНЖЕНЕРНОГО ИНСТИТУТА, СОДЕРЖАЩИЙСЯ В НАСТОЯЩЕМ ДОКУМЕНТЕ, ПОСТАВЛЯЕТСЯ КАК ЕСТЬ. УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ, ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, В ОТНОШЕНИИ ЛЮБЫХ ВОПРОСОВ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ, ГАРАНТИИ ПРИГОДНОСТИ ДЛЯ ЦЕЛЕЙ ИЛИ ТОВАРНОЙ ПРИГОДНОСТИ, ИСКЛЮЧИТЕЛЬНОСТИ ИЛИ РЕЗУЛЬТАТОВ, ПОЛУЧЕННЫХ ОТ ИСПОЛЬЗОВАНИЯ УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ В ОТНОШЕНИИ СВОБОДЫ ОТ ПАТЕНТА, ТОВАРНОГО ЗНАКА ИЛИ НАРУШЕНИЯ АВТОРСКИХ ПРАВ.

Данное программное обеспечение и связанная с ним документация не были рассмотрены и не одобрены Университетом Карнеги-Меллона или его Институтом разработки программного обеспечения.