Подтвердить что ты не робот

С++ ловкая ссылка

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

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

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

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

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

4b9b3361

Ответ 1

Существуют решения на основе времени выполнения, которые помогают коду проверять недопустимые обращения к указателям. До сих пор я использовал брызговик (который интегрирован в 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!
}

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

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

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

  • mudflap может быть точно настроен на то, что именно он должен проверять и делать. Подробнее читайте http://gcc.gnu.org/wiki/Mudflap_Pointer_Debugging.
  • Поведение по умолчанию - поднять SIGSEGV на нарушение, это означает, что вы можете найти нарушение в своем отладчике.
  • mudflap может быть сукой, в частности, когда вы взаимодействуете с библиотеками, которые не скомпилированы с поддержкой mudflap.
  • Не будет лаять на том месте, где создается ссылка на свидание (return S (value)), только когда ссылка разыменована. Если вам это нужно, вам понадобится инструмент статического анализа.

P.S. одна вещь, которую следует учитывать, - добавить НЕРАСПРОСТРАНЕННЫЙ к конструктору копирования S(), который утверждает, что значение_ не привязано к целому числу с более коротким сроком службы (например, если * это находится в "более старом" слоте стека, к которому привязано целое число). Это высокопроизводительная машина и, возможно, сложна, чтобы получить право, конечно, но должно быть хорошо, как только это будет только для отладки.

Ответ 2

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

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

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

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; };
};

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

@@[email protected]$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 

@@[email protected]$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 

@@[email protected]$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 

@@[email protected]$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 

Определите разницу? Нет.

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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

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

Когда вы публикуете вопрос в любом месте в Интернете, если этот вопрос включает код, ОТВЕТ НА ЭКСТРЕМЕННЫЙ КОД.

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

До тех пор, пока вы не пытаетесь разыменовать оборванную ссылку, код совершенно безопасен.

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

Ответ 6

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

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

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

Ответ 7

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

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

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

S& s2 = function(); // invalid

Это явно разрешено в С++ standard.

См. 12.2.4:

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

и 12.2.5:

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