Ловля C++ повисшая ссылка

Предположим следующая часть кода

struct S {
    S(int & value): value_(value) {}
    int & value_;
};

S function() {
    int value = 0;
    return S(value);   // implicitly returning reference to local value
}

компилятор не производит предупреждение (-Стена), эту ошибку может быть трудно зафиксировать.

Что инструменты там, чтобы помочь поймать такие проблемы

13
задан Anycorn 7 July 2010 в 21:38
поделиться

7 ответов

Существуют решения, основанные на времени выполнения, которые инструментируют код для проверки доступа к недействительным указателям. Пока что я использовал только брызговик (который интегрирован в GCC с версии 4.0). mudflap пытается отслеживать каждый указатель (и ссылку) в коде и проверяет каждый доступ, если указатель / ссылка действительно указывает на живой объект его базового типа. Вот пример:

#include <stdio.h>
struct S {
    S(int & value): value_(value) {}
    int & value_;
};

S function() {
    int value = 0;
    return S(value);   // implicitly returning reference to local value
}
int main()
{
    S x=function();
    printf("%s\n",x.value_); //<-oh noes!
}

Скомпилируйте это с включенным брызговиком:

g++ -fmudflap s.cc -lmudflap

и бегом вы получите:

$ ./a.out
*******
mudflap violation 1 (check/read): time=1279282951.939061 ptr=0x7fff141aeb8c size=4
pc=0x7f53f4047391 location=`s.cc:14:24 (main)'
      /opt/gcc-4.5.0/lib64/libmudflap.so.0(__mf_check+0x41) [0x7f53f4047391]
      ./a.out(main+0x7f) [0x400c06]
      /lib64/libc.so.6(__libc_start_main+0xfd) [0x7f53f358aa7d]
Nearby object 1: checked region begins 332B before and ends 329B before
mudflap object 0x703430: name=`argv[]'
bounds=[0x7fff141aecd8,0x7fff141aece7] size=16 area=static check=0r/0w liveness=0
alloc time=1279282951.939012 pc=0x7f53f4046791
Nearby object 2: checked region begins 348B before and ends 345B before
mudflap object 0x708530: name=`environ[]'
bounds=[0x7fff141aece8,0x7fff141af03f] size=856 area=static check=0r/0w liveness=0
alloc time=1279282951.939049 pc=0x7f53f4046791
Nearby object 3: checked region begins 0B into and ends 3B into
mudflap dead object 0x7089e0: name=`s.cc:8:9 (function) int value'
bounds=[0x7fff141aeb8c,0x7fff141aeb8f] size=4 area=stack check=0r/0w liveness=0
alloc time=1279282951.939053 pc=0x7f53f4046791
dealloc time=1279282951.939059 pc=0x7f53f4046346
number of nearby objects: 3
Segmentation fault

Пара моментов, на которые следует обратить внимание:

  1. брызговик можно точно настроить в том, что именно он должен проверять и делать. прочтите http://gcc.gnu.org/wiki/Mudflap_Pointer_Debugging , чтобы узнать подробности.
  2. По умолчанию при нарушении генерируется сигнал SIGSEGV, это означает, что вы можете найти нарушение в отладчике.
  3. Брызговик может быть неприятным, особенно когда вы взаимодействуете с библиотеками, которые не скомпилированы с поддержкой брызговика.
  4. Он не будет лаять в том месте, где создается висячая ссылка (вернуть S (значение)), только когда ссылка разыменована. Если вам это нужно, вам понадобится инструмент статического анализа.

П.С.нужно было подумать о том, чтобы добавить проверку NON-PORTABLE к конструктору копирования S (), который утверждает, что value_ не привязан к целому числу с более коротким сроком службы (например, если * это находится в «старом» слоте стека, к которому он привязан целым числом). Это в высшей степени специфично для машины и, конечно, может быть сложно исправить, но должно быть в порядке, пока это только для отладки.

11
ответ дан 1 December 2019 в 22:54
поделиться

Я не думаю, что какой-либо статический инструмент может это обнаружить, но если вы используете Valgrind вместе с некоторыми модульными тестами или любым другим кодом, который падает (seg fault), вы можете легко найти, куда ссылается память и где она была выделена изначально.

1
ответ дан 1 December 2019 в 22:54
поделиться

Я думаю, что все это отловить невозможно, хотя некоторые компиляторы могут выдавать предупреждения в некоторых случаях.

Также следует помнить, что ссылки - это указатели под капотом, и многие из сценариев "выстрела себе под ноги", возможных с указателями, все еще возможны...

Чтобы прояснить, что я имею в виду под "указателями под капотом", возьмем два следующих класса. Один использует ссылки, другой - указатели.

class Ref
{
  int &ref;
public:
  Ref(int &r) : ref(r) {};
  int get() { return ref; };
};

class Ptr
{
  int *ptr;
public:
  Ptr(int *p) : ptr(p) {};
  int get() { return *ptr; };
};

Теперь сравните сгенерированный код для этих двух классов.

@@Ref@$bctr$qri proc    near  // Ref::Ref(int &ref)
    push      ebp
    mov       ebp,esp
    mov       eax,dword ptr [ebp+8]
    mov       edx,dword ptr [ebp+12]
    mov       dword ptr [eax],edx
    pop       ebp
    ret 

@@Ptr@$bctr$qpi proc    near  // Ptr::Ptr(int *ptr)
    push      ebp
    mov       ebp,esp
    mov       eax,dword ptr [ebp+8]
    mov       edx,dword ptr [ebp+12]
    mov       dword ptr [eax],edx
    pop       ebp
    ret 

@@Ref@get$qv    proc    near // int Ref:get()
    push      ebp
    mov       ebp,esp
    mov       eax,dword ptr [ebp+8]
    mov       eax,dword ptr [eax]
    mov       eax,dword ptr [eax]
    pop       ebp
    ret 

@@Ptr@get$qv    proc    near // int Ptr::get()
    push      ebp
    mov       ebp,esp
    mov       eax,dword ptr [ebp+8]
    mov       eax,dword ptr [eax]
    mov       eax,dword ptr [eax]
    pop       ebp
    ret 

Видите разницу? Никакой.

5
ответ дан 1 December 2019 в 22:54
поделиться

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

Если вы имели в виду , вместо этого верните S (значение) , тогда, ради всего святого, КОПИРОВАТЬ, ВСТАВЬТЕ КОД, ПУБЛИКОВАННЫЙ ЗДЕСЬ .

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

Когда вы публикуете вопрос в Интернете, если этот вопрос включает код, НАПИСАТЬ ТОЧНЫЙ КОД .

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

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

Возможно, некоторые инструменты статического анализа (например, Valgrind или MSVC с / analysis) могут предупредить вас об этом, но в этом нет особого смысла, потому что вы не делаете ничего плохого. Вы возвращаете объект, который содержит висящую ссылку. Вы напрямую не возвращаете ссылку на локальный объект (о котором компиляторы обычно предупреждают ), а на объект более высокого уровня с поведением, которое может сделать его совершенно безопасным для использования, даже если он содержит ссылку на локальный объект, который вышел за пределы области видимости.

0
ответ дан 1 December 2019 в 22:54
поделиться

Есть руководство, которому я следую после того, как меня побили именно этим:

Когда в классе есть член-ссылка (или указатель на что-то, что может иметь время жизни, которое вы не контролируете), сделайте объект некопируемым.

Таким образом, вы уменьшите вероятность выхода из области видимости с висячей ссылкой.

1
ответ дан 1 December 2019 в 22:54
поделиться

Это совершенно правильный код.

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

const S& s1 = function(); // valid

S& s2 = function(); // invalid

Это явно разрешено в стандарте C ++ .

См. 12.2.4:

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

и 12.2.5:

Второй контекст - это когда ссылка привязана к временному объекту. Временный объект, к которому привязана ссылка, или временный объект, являющийся полным объектом подобъекта, к которому привязана ссылка, сохраняется в течение всего времени существования ссылки, за исключением: [...]

0
ответ дан 1 December 2019 в 22:54
поделиться

Вы должны использовать технологию, основанную на инструментах времени компиляции. Хотя valgrind мог проверять все вызовы функций во время выполнения (malloc, free), он не мог проверять только код .

В зависимости от вашей архитектуры, IBM PurifyPlus найдет некоторые из этих проблем. Поэтому вам следует найти действующую лицензию (или использовать лицензию вашей компании) для ее использования или попробовать ее с пробной версией.

2
ответ дан 1 December 2019 в 22:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: