8

我最近发现我的 C++ 程序中的大多数错误都类似于以下示例:

#include <iostream>

class Z
{
 public:
 Z(int n) : n(n) {}
 int n;
};

class Y
{
 public:
 Y(const Z& z) : z(z) {}
 const Z& z;
};

class X
{
 public:
 X(const Y& y) : y(y) {}
 Y y;
};

class Big
{
 public:
 Big()
 {
   for (int i = 0; i < 1000; ++i) { a[i] = i + 1000; }
 }
 int a[1000];
};

X get_x() { return X(Y(Z(123))); }

int main()
{
 X x = get_x();
 Big b;
 std::cout << x.y.z.n << std::endl;
}

输出:1000

我希望这个程序输出 123(在 get_x() 中设置的 xyzn 的值),但是“Big b”的创建覆盖了临时 Z。结果,对象 Y 中对临时 Z 的引用现在被覆盖了大 b,因此输出不是我所期望的。

当我使用带有选项“-Wall”的 gcc 4.5 编译这个程序时,它没有给出任何警告。

解决方法显然是从 Y 类中的成员 Z 中删除引用。但是,通常 Y 类是我尚未开发的库的一部分(最近的 boost::fusion),此外情况要复杂得多比我给出的这个例子。

这有某种 gcc 选项,或者任何其他软件可以让我最好在编译时检测到此类问题,但即使运行时也总比没有好?

谢谢,

克林顿

4

3 回答 3

2

几个月前,我在 clang-dev 邮件列表上提交了此类案例,但当时没有人有时间处理它(不幸的是,我也没有)。

不过,Argyrios Kyrtzidis 目前正在研究它,这是他对此事的最后更新(格林威治标准时间 11 月 30 日 23 时 04 分):

我恢复了之前的提交,更好地修复了 http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20101129/036875.html。例如对于

struct S {   int x; };

int &get_ref() {   S s;   S &s2 = s;   int &x2 = s2.x;   return x2; }

我们得到

t3.cpp:9:10: warning: reference to stack memory associated with local variable 's' returned
  return x2;
         ^~
t3.cpp:8:8: note: binding reference variable 'x2' here
  int &x2 = s2.x;
       ^    ~~
t3.cpp:7:6: note: binding reference variable 's2' here
  S &s2 = s;
     ^    ~
1 warning generated.

之前的尝试未能通过自托管测试,所以我希望这次尝试能够通过。无论如何,我很高兴 Argyrios 正在调查它:)

诚然,它还不完美,因为这是一个相当复杂的问题要解决(在某种程度上让我想起了指针别名),但这仍然是朝着正确方向迈出的一大步。

你能用这个版本的 Clang 测试你的代码吗?我很确定 Argyrios 会欣赏反馈(无论是否检测到)。

于 2010-12-01T08:56:42.173 回答
0

[编辑第三个项目符号以演示可能有帮助的技术] 当一种语言允许使用相同的调用者语法按值或引用传递参数时,这就是你陷入的兔子洞。您有以下选择:

  • 将参数更改为非常量引用。临时值与非常量引用类型不匹配。

  • 在可能的情况下完全删除参考。如果您的 const 引用未指示调用者和被调用者之间的逻辑共享状态(如果他们这样做,则此问题不会经常发生),则可能插入它们是为了避免复杂类型的幼稚复制。现代编译器具有先进的复制省略优化,在大多数情况下使按值传递与按引用传递一样有效;见http://cpp-next.com/archive/2009/08/want-speed-pass-by-value一个很好的解释。如果您将值传递给可能会修改临时对象的外部库函数,则显然不会执行复制省略,但如果是这种情况,那么您要么不将它们作为 const 引用传递,要么故意丢弃 const -ness 在原始版本中。这是我的首选解决方案,因为它让编译器不必担心复制优化,让我不必担心代码中的其他错误来源。

  • 如果您的编译器支持右值引用,请使用它们。如果您至少可以编辑您担心此问题的函数的参数类型,则可以定义一个包装元类,如下所示:

模板<类型名T>类需要参考{

T&ref_;

上市:

need_ref(T &&x) { /* 没有 */ }

need_ref(T &x) : ref_(x) { /* 没有 */ }

运算符 T & () { return ref_; }

};

然后将 T& 类型的参数替换为需要类型的参数。例如,如果您定义以下

类用户{

诠释 &z;

上市:

user(need_ref<int> arg) : z(arg) { /* 没有 */ }

};

那么您可以使用“int a = 1, b = 2; user ua(a);”形式的代码安全地初始化 user 类型的对象,但如果您尝试初始化为“user sum(a+b)”或“用户五(5)”你的编译器应该在第一个版本的 need_ref() 构造函数中生成一个未初始化的引用错误。该技术显然不限于构造函数,并且不会产生运行时开销。

于 2010-12-01T02:42:42.043 回答
-1

这里的问题是代码

 Y(const Z& z) : z(z) {}

因为成员“z”是通过对形式参数“z”的引用来初始化的。一旦构造函数返回,引用就指向一个不再有效的对象。

我认为编译器在很多情况下不会或不能检测到这样的逻辑缺陷。IMO 的修复显然是要了解这些类并以适合其设计的方式使用它们。这应该由库供应商真正记录下来。

顺便说一句,如果可能,最好将成员 'Y::z' 命名为 'Y::mz'。表达“z(z)”不是很吸引人

于 2010-12-01T02:44:01.950 回答