假设以下代码
struct S {
S(int & value): value_(value) {}
int & value_;
};
S function() {
int value = 0;
return S(value); // implicitly returning reference to local value
}
编译器不会产生警告(-Wall),这个错误很难捕获。
有哪些工具可以帮助解决这些问题
答案 0 :(得分:10)
有基于运行时的解决方案,用于检测代码以检查无效指针访问。到目前为止我只使用过mudflap(自4.0版以来就集成在GCC中)。 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
要考虑的几点:
P.S。要考虑的一件事是,将 NON-PORTABLE 检查添加到S()的复制构造函数中,该复制构造函数断言value_未绑定到寿命较短的整数(例如,如果*它位于堆栈的“较旧”插槽中,它所绑定的整数)。这是高度机器特定的,当然可能很难做到,但只要它只用于调试就应该没问题。
答案 1 :(得分:4)
我认为这是不可能捕获所有这些,虽然有些编译器可能会在某些情况下发出警告。
同样要记住,引用本身就是引导之下的指针,并且许多可以使用指针进行自拍的场景仍然是可能的。
为了澄清我对“引擎盖下的指针”的意思,请参加以下两个课程。一个使用引用,其他指针。
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 :(得分:2)
您必须使用基于编译时检测的技术。虽然valgrind可以在运行时检查所有函数调用(malloc,free),但它无法仅检查代码。
根据您的体系结构, IBM PurifyPlus 会发现其中一些问题。因此,您应该找到有效的许可证(或使用您公司的许可证)来使用它,或者尝试使用试用版。
答案 3 :(得分:1)
我不认为任何静态工具都可以捕获它,但是如果你使用Valgrind以及一些单元测试或任何代码崩溃(seg fault),你可以很容易地找到引用内存的位置和最初分配的地方。
答案 4 :(得分:1)
您的代码甚至不应该编译。我所知道的编译器将无法编译代码,或者至少会发出警告。
如果您的意思是return S(value)
,那么为了天堂的缘故 COPY PASTE您在此发布的代码。
重写和引入拼写错误只是意味着我们不可能实际猜出你询问哪些错误,哪些是我们应该忽略的意外。
当您在互联网上的任何地方发布问题时,如果该问题包含代码,发布完整代码。
现在,假设这实际上是一个错字,代码是完全合法的,并且任何工具都没有理由警告你。
只要您不尝试取消引用悬空参考,代码就非常安全。
有些静态分析工具(例如Valgrind或带有/ analyze的MSVC)可能会对此发出警告,但似乎没有太大意义,因为你没有做错任何事情。你正在返回一个恰好包含悬空引用的对象。您不是直接返回对本地对象的引用(通常做警告的编译器),而是具有可能使其使用完全安全的行为的更高级别对象,即使它包含引用到超出范围的本地对象。
答案 5 :(得分:1)
在被这个确切的事情打败后,我遵循了一条准则:
当一个类有一个引用成员(或指向一个你可以控制的生命周期的指针)时,使该对象不可复制。
这样,您可以减少使用悬空参考转义范围的机会。
答案 6 :(得分:0)
这是完全有效的代码。
如果调用函数并将临时值绑定到const引用,则范围会延长。
const S& s1 = function(); // valid
S& s2 = function(); // invalid
C++ standard明确允许这样做。
见12.2.4:
有两种情况可以在与完整表达结束时不同的时间点销毁临时表。
和12.2.5:
第二个上下文是引用绑定到临时的。引用绑定的临时值或作为绑定引用的子对象的完整对象的临时值在引用的生命周期内持续存在,除了:[...]