我花了很多时间调试其中一个程序中一个晦涩的内存损坏问题。从本质上讲,它归结为一个函数,该函数通过以将其传递到对象构造函数中的方式调用值来返回结构。伪代码如下。
extern SomeStructure someStructureGenerator();
class ObjectWhichUsesStructure {
ObjectWhichUsesStructure(const SomeStructure& ref): s(ref) {}
const SomeStructure& s;
}
ObjectWhichUsesStructure obj(someStructureGenerator());
我的理由是:someStructureGenerator()
返回一个临时目录;这被绑定到const引用,这意味着编译器正在扩展临时文件的寿命以匹配使用位置;我正在用它来构造一个对象,因此临时寿命得以延长以匹配该对象的寿命。
最后一点事实并非如此。构造函数退出后,编译器将删除该临时文件,现在obj
包含对超空间的引用,当我尝试使用它时会产生可笑的结果。我需要将const引用显式绑定到作用域,如下所示:
const auto& ref = someStructureGenerator();
ObjectWhichUsesStructure obj(ref);
这不是我要问的问题。
我要问的是:我的编译器是gcc 8,我用-Wall
进行编译,非常高兴用-干净地编译上面的代码没有警告。我的程序在valgrind下愉快(但不正确)运行,同样没有警告。
我不知道我的代码中有多少其他地方在使用相同的习惯用法。哪种编译器工具会检测并标记这些位置,以便我可以修复代码,并在以后遇到相同的错误时提醒我?
答案 0 :(得分:1)
首先,引用绑定确实延长了生命周期,但仅延长了 constructor参数的生命周期。 s(ref)
不会绑定对象(因为ref
已经是引用),因此不会发生进一步的扩展。
因此可以通过聚合初始化来执行您期望的扩展:
struct ObjectWhichUsesStructure {
const SomeStructure &s;
};
ObjectWhichUsesStructure obj{someStructureGenerator()};
这里没有构造函数参数(因为根本没有构造函数!),因此只有一个所需的绑定发生。
值得一提的是,为什么编译器不会对此发出警告:即使构造函数确实保留了对临时参数的引用,在某些情况下,合法情况仍然有效:
void useWrapper(ObjectWhichUsesStructure);
void f() {useWrapper(someStructureGenerator());}
SomeStructure
的生存直到声明的结尾,在这段时间内useWrapper
可以利用ObjectWhichUsesStructure
中的引用来获利。
以牺牲禁止上述有效用例为代价,您可以通过提供一个已删除构造函数并使用一个右值引用,来使编译器捕获有问题的情况:
struct ObjectWhichUsesStructure {
ObjectWhichUsesStructure(const SomeStructure& ref): s(ref) {}
ObjectWhichUsesStructure(SomeStructure&&)=delete;
const SomeStructure& s;
};
这可能值得暂时作为一种诊断手段,而不必将其永久地限制。