这是对大括号初始化列表的不安全使用吗?
Posted
技术标签:
【中文标题】这是对大括号初始化列表的不安全使用吗?【英文标题】:Is this unsafe usage of a braced initializer list? 【发布时间】:2017-04-25 11:26:48 【问题描述】:我最近在我的程序中出现了一个错误,这让我有点吃惊,但也许它不应该,因为 C++(尤其是现代 C++)提供了大量的初始化类型。我有一个看起来像这样的类:
struct foo
foo(const std::set<int> &values) values_ = values;
set::set<int> values_;
;
在构造foo
s 时,通常最简单的方法是使用初始值设定项列表内联指定一组值(一个实例通常有 2-3 个在编译时已知的已知值),如下所示:
auto f = std::make_shared<foo>( 1, 2, 3 );
然而,我记得当我第一次写课程foo
时,上面的用法给我带来了编译时问题;我不记得确切的错误,但我发现显式调用 initializer_list
构造函数允许编译器正确推断出如何处理参数,如下所示:
auto f = std::make_shared<foo>(std::initializer_list<int>( 1, 2, 3 ));
这“工作”了很长时间,但我的应用程序最近遇到了一些模糊的、看似随机的崩溃。调试后发现上面改成:
auto f = std::make_shared<foo>(std::initializer_list<int> 1, 2, 3 );
(即从复制初始化更改为大括号初始化程序)解决了这个问题。这让我想到了一个问题:
如上例所示的初始化列表的复制构造是不安全的做法吗?大括号初始值设定项中值的生命周期是否与全封闭表达式的生命周期不匹配?
这可能只是其他一些仍然存在的错误或某种编译器错误的症状(我使用的是 Apple clang v8.0)。
【问题讨论】:
请显示您的构造函数定义。 @KerrekSB:已添加。我真正要做的就是复制作为参数传入的set
。这是一个非常简化的版本;启发foo
的实际课程要复杂得多。但是,对set
进行的唯一操作如示例所示。
你遇到的bug与这里的初始化无关。通过一些动态清理程序(Valgrind、ASAN、TSAN)运行您的程序以找到真正的错误。
我看不出你有什么问题。如果你可以给我们一个minimal reproducible example 应该有助于找到问题。
感谢您的见解。最小化重现的测试用例将是一项艰巨的任务;我只是对这个特殊的构造感到好奇,因为它似乎对失败有直接影响,而且我对所有管理支撑初始化的规则还没有那么自信。正如@KerrekSB 指出的那样,我需要使用更多的分析工具来在这个问题上归零。
【参考方案1】:
临时初始化列表应该持续到它们构造的语句结束,并且它们的生命周期会扩展它们包装的数组。
虽然 make_shared<?>(1,2,3)
预计不会工作(请参阅完美转发的缺陷),但您的其余代码应该可以。如果您描述的更改实际上导致代码开始/停止工作,那么您有一些内存损坏。
如果这很有可能是确定性的,那么问题很可能是有人对堆栈上的对象有一个悬空引用,并且正在修改或读取它。当您更改构造初始化列表的方式时,堆栈上的对象布局会发生变化,并且会发生不同的行为(某些值非零或零并且分支采用不同的方式,或者写入的内容无关紧要在一种情况下,在另一种情况下确实如此)。
如果是基于写入的内存损坏,设置内存断点并跟踪对堆栈的该区域的所有访问(尽管可能很乏味)有时可以发现正在跟踪危险指针的位置。
【讨论】:
以上是关于这是对大括号初始化列表的不安全使用吗?的主要内容,如果未能解决你的问题,请参考以下文章