На моем gcc-4.8.1 я скомпилировал следующую программу с двумя командами:
g++ -Wfatal-errors -std=c++11 -Wall -Werror test.cpp -o test -g
g++ -Wfatal-errors -std=c++11 -Wall -Werror test.cpp -o test -O3 -g
Первый исполняемый файл имеет ожидаемый результат, а второй — ошибочный. Проблема в том, что трудно отлаживать, потому что -O3
портит код слишком много для -g
отладочная информация, чтобы сохранить значение, так gdb
имеет проблемы с переводом того, что происходит в исходном коде. Итак, я вместо этого начал вставлять операторы печати. Как я и ожидал, операторы print меняют результат. С отладочными отпечатками это работает просто отлично!
Вот мой источник шаблона выражения:
//test.cpp
#include <vector>
#include <stdlib.h>
#include <iostream>
using namespace std;
typedef vector<int> Valarray;
template<typename L, typename R>
struct BinOpPlus {
const L& left;
const R& right;
BinOpPlus(const L& l, const R& r)
: left(l), right(r)
{}
int operator[](int i) const {
int l = left[i];
//cerr << "Left: " << l << endl; //uncomment to fix segfault
int r = right[i];
//cerr << "Right: " << r << endl; //uncomment to fix segfault
return l + r;
}
};
template<typename L, typename R>
BinOpPlus<L, R> operator+(const L& left, const R& right){
return BinOpPlus<L, R>(left, right);
}
int main() {
//int size = 10000000;
int size = 10;
Valarray v[3];
for(int n=0; n<3; ++n){
for(int i=0; i<size; ++i){
int val = rand() % 100;
v[n].push_back(val);
}
}
auto out = v[0] + v[1] + v[2];
int sum = 0;
for(int i=0; i<size; ++i){
cerr << "Checkpoint!" << endl;
sum += out[i]; //segfaults here
cerr << "Sum: " << sum << endl;
}
cout << "Sum: " << sum << endl;
return 0;
}
Прошло много времени с тех пор -O3
дал мне неверный / ненадежный двоичный файл. Сначала я предполагаю, что я сделал что-то не так в своем коде, но недостаточно -O0
показать это. У кого-нибудь есть идеи, что я делаю не так?
В этой строке
auto out = v[0] + v[1] + v[2];
Тип out
является BinOpPlus< BinOpPlus<ValArray, ValArray>, Valarray>
, Так как ваш BinOpPlus
хранит ссылки на свои аргументы, а BinOpPlus<ValArray,ValArray>
есть временное, у вас неопределенное поведение.
Обычно шаблоны выражений, подобные этим, используют черту, чтобы решить, как хранить их аргументы, так что вы можете хранить реальные объекты по ссылке (и предполагать, что пользователь не испортит) и другие ET по значению (они в любом случае очень малы).
Также используя auto
с арифметическими инопланетянами считается, по крайней мере, проблематичным, поскольку он редко производит намеченный тип. По этой самой причине было несколько предложений о введении operator auto
настроить тип, выводимый auto в ET.
Это догадка.
В соответствии
auto out = v[0] + v[1] + v[2];
у вас есть временный объект. Это может быть удалено с -O3
флаг. Я бы попробовал следующее:
auto out1 = v[1] + v[2];
auto out = v[0] + out1;
Измените свой код, чтобы не использовать ссылочные элементы в вашей структуре. Я полагаю, что ссылочные члены делают операцию копирования бесполезной, когда вы делаете дополнение здесь:
auto out = v[0] + v[1] + v[2];
Например:
template<typename L, typename R>
struct BinOpPlus {
const L left;
const R right;
Внесение этого изменения работает правильно.
Кроме того, к вашему сведению, при компиляции вашего кода с Visual Studio 2013
с полными предупреждениями (/W4
), мы получаем это предупреждение:
предупреждение C4512: «BinOpPlus»: оператор присваивания не может быть создан.
Таким образом, это указывает на то, что любое копирование может привести к нежелательным последствиям.
Живой пример хорошего бега без ссылок: http://ideone.com/JKxoDv
Живой пример плохого запуска со ссылками: http://ideone.com/7oSoJB