OpenMP con MSVC 2010 Debug genera un error extraño cuando se copia un objeto

Tengo un programa bastante complejo que se ejecuta en un comportamiento extraño cuando se compila con OpenMP en el modo de depuración de MSVC 2010. He intentado mi mejor esfuerzo para construir el siguiente ejemplo de trabajo mínimo (aunque no es realmente mínimo) que explica la estructura del programa real.

#include <vector>
#include <cassert>

// A class take points to the whole collection and a position Only allow access
// to the elements at that posiiton. It provide read-only access to query some
// information about the whole collection
class Element
{
    public :

    Element (int i, std::vector<double> *src) : i_(i), src_(src) {}

    int i () const {return i_;}
    int size () const {return src_->size();}

    double src () const {return (*src_)[i_];}
    double &src () {return (*src_)[i_];}

    private :

    const int i_;
    std::vector<double> *const src_;
};

// A Base class for dispatch
template <typename Derived>
class Base
{
    protected :

    void eval (int dim, Element elem, double *res)
    {
        // Dispatch the call from Evaluation<Derived>
        eval_dispatch(dim, elem, res, &Derived::eval); // Point (2)
    }

    private :

    // Resolve to Derived non-static member eval(...)
    template <typename D>
    void eval_dispatch(int dim, Element elem, double *res,
            void (D::*) (int, Element, double *))
    {
#ifndef NDEBUG // Assert that this is a Derived object
        assert((dynamic_cast<Derived *>(this)));
#endif
        static_cast<Derived *>(this)->eval(dim, elem, res);
    }

    // Resolve to Derived static member eval(...)
    void eval_dispatch(int dim, Element elem, double *res,
            void (*) (int, Element, double *))
    {
        Derived::eval(dim, elem, res); // Point (3)
    }

    // Resolve to Base member eval(...), Derived has no this member but derived
    // from Base
    void eval_dispatch(int dim, Element elem, double *res,
            void (Base::*) (int, Element, double *))
    {
        // Default behavior: do nothing
    }
};

// A middle-man who provides the interface operator(), call Base::eval, and
// Base dispatch it to possible default behavior or Derived::eval
template <typename Derived>
class Evaluator : public Base<Derived>
{
    public :

    void operator() (int N , int dim, double *res)
    {
        std::vector<double> src(N);
        for (int i = 0; i < N; ++i)
            src[i] = i;

#pragma omp parallel for default(none) shared(N, dim, src, res)
        for (int i = 0; i < N; ++i) {
            assert(i < N);
            double *r = res + i * dim;
            Element elem(i, &src);
            assert(elem.i() == i); // Point (1)
            this->eval(dim, elem, r);
        }
    }
};

// Client code, who implements eval
class Implementation : public Evaluator<Implementation>
{
    public :

    static void eval (int dim, Element elem, double *r)
    {
        assert(elem.i() < elem.size()); // This is where the program fails Point (4)
        for (int d = 0; d != dim; ++d)
            r[d] = elem.src();
    }
};

int main ()
{
    const int N = 500000;
    const int Dim = 2;
    double *res = new double[N * Dim];
    Implementation impl;
    impl(N, Dim, res);
    delete [] res;

    return 0;
}

El programa real no tienevector etc. Pero elElement, Base, Evaluator yImplementation Captura la estructura básica del programa real. Cuando se genera en el modo de depuración y se ejecuta el depurador, la aserción falla enPoint (4).

Aquí hay algunos detalles más de la información de depuración, al ver las pilas de llamadas,

Al entrarPoint (1), El locali tiene valor371152, lo cual está bien. La variableelem No aparece en el cuadro, lo cual es un poco extraño. Pero desde la aseveración enPoint (1) No faile, supongo que está bien.

Entonces, sucedieron cosas locas. La llamada aeval porEvaluator se resuelve a su clase base, y asiPoint (2) fue ejecutado En este punto, los depuradores muestran que elelem tienei_ = 499999, que ya no es eli utilizado para crearelem enEvaluator antes de pasarlopor valor aBase::eval. El siguiente punto, resuelvePoint (3), esta vez,elem tienei_ = 501682, que está fuera de rango, y este es el valor cuando la llamada se dirige aPoint (4) Y falló la afirmación.

Parece que cuandoElement objeto se pasa por valor, se cambia el valor de sus miembros. Vuelva a ejecutar el programa varias veces; si bien, no siempre se pueden reproducir comportamientos similares. En el programa real, esta clase está diseñada para que le guste un iterador, que itera sobre una colección de partículas. Aunque la cosa que itera no es exactamente como un contenedor. Pero de todos modos, el punto es que es lo suficientemente pequeño como para pasarlo de manera eficiente por valor. Y por lo tanto, el código del cliente, sabe que tiene su propia copia deElement en lugar de alguna referencia o puntero, y no tiene que preocuparse por la seguridad de subprocesos (mucho) siempre que se mantenga conElementLa interfaz, que solo proporciona acceso de escritura a una única posición de toda la colección.

Probé el mismo programa con GCC e Intel ICPC. No sucede nada inesperado. Y en el programa real, los resultados correctos fueron producidos.

¿Usé OpenMP incorrectamente en algún lugar? Pensé que elelem creado en aproximadamentePoint (1) será local al cuerpo del bucle. Además, en todo el programa, ningún valor mayor queN fue producido, entonces, ¿de dónde viene ese nuevo valor?

Editar

Miré más detenidamente el depurador, muestra que mientraselem.i_ fue cambiado cuandoelem fue pasado por valor, el punteroelem.src_ No cambia con eso. Tiene el mismo valor (de la dirección de memoria) después de pasado por valor

Editar: banderas del compilador

Utilicé CMake para generar la solución MSVC. Tengo que confesar que no tengo idea de cómo usar MSVC o Windows en general. La única razón por la que lo uso es que sé que mucha gente lo usa, por lo que quiero probar mi biblioteca para evitar cualquier problema.

El proyecto generado CMake, usandoVisual Studio 10 Win64 objetivo, las banderas del compilador parecen ser/DWIN32 /D_WINDOWS /W3 /Zm1000 /EHsc /GR /D_DEBUG /MDd /Zi /Ob0 /Od /RTC1 Y aquí está la línea de comandos que se encuentra en Páginas de propiedades-C / C ++ - Línea de comandos/Zi /nologo /W3 /WX- /Od /Ob0 /D "WIN32" /D "_WINDOWS" /D "_DEBUG" /D "CMAKE_INTDIR=\"Debug\"" /D "_MBCS" /Gm- /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /GR /openmp /Fp"TestOMP.dir\Debug\TestOMP.pch" /Fa"Debug" /Fo"TestOMP.dir\Debug\" /Fd"C:/Users/Yan Zhou/Dropbox/Build/TestOMP/build/Debug/TestOMP.pdb" /Gd /TP /errorReport:queue

¿Hay algo sospechoso aquí?

Respuestas a la pregunta(1)

Su respuesta a la pregunta