C ++ ловит висячую ссылку - PullRequest
13 голосов
/ 08 июля 2010

Предположим, следующий фрагмент кода

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

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

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

Какие существуют инструменты, помогающие справиться с такими проблемами

Ответы [ 7 ]

11 голосов
/ 16 июля 2010

Существуют решения на основе времени выполнения, которые инструментируют код для проверки доступа к недействительным указателям. До сих пор я использовал только mudflap (который интегрирован в 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. mudflap может быть точно настроен на то, что именно он должен проверять и делать. Подробнее читайте http://gcc.gnu.org/wiki/Mudflap_Pointer_Debugging.
  2. Поведение по умолчанию - поднять SIGSEGV при нарушении, это означает, что вы можете найти нарушение в своем отладчике.
  3. mudflap может быть сукой, особенно когда вы взаимодействуете с библиотеками, которые не скомпилированы с поддержкой mudflap.
  4. Он не будет лаять в месте, где создается свисающая ссылка (возвращает S (значение)), только когда ссылка разыменована. Если вам это нужно, вам понадобится инструмент статического анализа.

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

4 голосов
/ 08 июля 2010

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

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

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

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 

Найдите разницу? Там нет ни одного.

2 голосов
/ 20 июля 2010

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

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

1 голос
/ 16 июля 2010

Существует правило, которому я следую после избиения этой точной вещью:

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

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

1 голос
/ 08 июля 2010

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

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

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

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

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

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

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

1 голос
/ 08 июля 2010

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

0 голосов
/ 21 июля 2010

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

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

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

S& s2 = function(); // invalid

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

См. 12.2.4:

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

и 12.2.5:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...