将 std::function class 成员与编译器优化一起使用时出现段错误
Segfault when using std::function class member with compiler optimizations
我有一个非常简单的代码示例,它在 gcc 8.2.0
下使用 -O2
优化时崩溃
#include <vector>
#include <functional>
#include <iostream>
template<typename T, typename Container>
class Lambda_Expression
{
using Lambda = std::function<T()>;
const Lambda & _lambda;
public:
Lambda_Expression(const Lambda & l) : _lambda(l) {}
T operator[](const std::size_t i)
{
std::cerr << "inside expression [] " << i << std::endl;
return _lambda();
}
};
auto lambda = []() -> double
{
return 1.0;
};
int main()
{
int N = 10;
std::vector<double> res(N, 0.0);
double x = lambda();
std::cerr << "before for loop " << x << std::endl;
auto test_expression = Lambda_Expression<double, std::vector<double>>(lambda);
for( int idx=0; idx<N; ++idx )
{
std::cerr << "loop " << idx << std::endl;
double x = test_expression[idx];
}
}
也使用 -std=c++17
,以防有差异。
我明白了
before for loop 1
loop 0
inside expression [] 0
[1] 5288 segmentation fault ./bench_lambdas
而我希望循环 运行 进行 10 次迭代。优化级别小于 2 时不会出现此段错误。
上面的例子对我来说看起来是相当无害的代码,据我所知,2 级优化不应该破坏正确的代码。
问题:
我的示例中是否存在未定义的行为或不正确的代码,或者可能是什么问题?
据我所知,这是未定义的行为。
问题是您的 class 注册了引用
// ..........V reference !!!
const Lambda & _lambda;
构造函数的参数
Lambda_Expression(const Lambda & l) : _lambda(l) {}
那是一个std::function
using Lambda = std::function<T()>;
但是当您使用 lambda 调用构造函数时(如 main()
)
auto test_expression = Lambda_Expression<double, std::vector<double>>(lambda);
您在 _lambda
中保存了对临时对象的引用,因为 lambda
不是 std::function
,所以它创建了一个 临时 对象,类型 std::function<double()>
,初始化为 lambda
。
所以问题是:对临时对象的引用在 test_expression
的构造结束时变成悬空引用 所以,当你调用 test_expression[idx]
时,你使用 _lambda
正(可能)指向垃圾。
我建议避免此类问题,避免引用部分(使 _lambda
成为 std::function
类型的常规成员
const Lambda _lambda; // <-- no more reference
所以你复制临时对象)
但是如果你真的希望_lambda
是对std::function
的引用,你应该这样写
std::function<double()> f{lambda};
auto test_expression = Lambda_Expression<double, std::vector<double>>{f};
这样构造函数会收到对 std::function
对象 (f
) 的引用,该对象在他的调用中仍然存在。
我有一个非常简单的代码示例,它在 gcc 8.2.0
下使用-O2
优化时崩溃
#include <vector>
#include <functional>
#include <iostream>
template<typename T, typename Container>
class Lambda_Expression
{
using Lambda = std::function<T()>;
const Lambda & _lambda;
public:
Lambda_Expression(const Lambda & l) : _lambda(l) {}
T operator[](const std::size_t i)
{
std::cerr << "inside expression [] " << i << std::endl;
return _lambda();
}
};
auto lambda = []() -> double
{
return 1.0;
};
int main()
{
int N = 10;
std::vector<double> res(N, 0.0);
double x = lambda();
std::cerr << "before for loop " << x << std::endl;
auto test_expression = Lambda_Expression<double, std::vector<double>>(lambda);
for( int idx=0; idx<N; ++idx )
{
std::cerr << "loop " << idx << std::endl;
double x = test_expression[idx];
}
}
也使用 -std=c++17
,以防有差异。
我明白了
before for loop 1
loop 0
inside expression [] 0
[1] 5288 segmentation fault ./bench_lambdas
而我希望循环 运行 进行 10 次迭代。优化级别小于 2 时不会出现此段错误。
上面的例子对我来说看起来是相当无害的代码,据我所知,2 级优化不应该破坏正确的代码。
问题: 我的示例中是否存在未定义的行为或不正确的代码,或者可能是什么问题?
据我所知,这是未定义的行为。
问题是您的 class 注册了引用
// ..........V reference !!!
const Lambda & _lambda;
构造函数的参数
Lambda_Expression(const Lambda & l) : _lambda(l) {}
那是一个std::function
using Lambda = std::function<T()>;
但是当您使用 lambda 调用构造函数时(如 main()
)
auto test_expression = Lambda_Expression<double, std::vector<double>>(lambda);
您在 _lambda
中保存了对临时对象的引用,因为 lambda
不是 std::function
,所以它创建了一个 临时 对象,类型 std::function<double()>
,初始化为 lambda
。
所以问题是:对临时对象的引用在 test_expression
的构造结束时变成悬空引用 所以,当你调用 test_expression[idx]
时,你使用 _lambda
正(可能)指向垃圾。
我建议避免此类问题,避免引用部分(使 _lambda
成为 std::function
const Lambda _lambda; // <-- no more reference
所以你复制临时对象)
但是如果你真的希望_lambda
是对std::function
的引用,你应该这样写
std::function<double()> f{lambda};
auto test_expression = Lambda_Expression<double, std::vector<double>>{f};
这样构造函数会收到对 std::function
对象 (f
) 的引用,该对象在他的调用中仍然存在。