c++ undefined-behavior pointer-arithmetic

c++ - Acceso a miembros de datos de estructura mediante aritmética de puntero



undefined-behavior pointer-arithmetic (4)

Es un comportamiento indefinido.

En general, la aritmética del puntero se define correctamente solo para los miembros de una matriz (y tal vez un elemento después, como se describe en la sección §8.5.6 de la standard ).

Para clases / estructuras, esto no puede funcionar, porque el compilador puede agregar relleno u otros datos entre los miembros. cppreference tiene una breve descripción del diseño de la clase.

Ahora, yendo a soluciones a su problema, el primero sería simplemente usar algo hecho para esto, como Eigen . Es una biblioteca madura para álgebra lineal, con código bien probado y buenas optimizaciones.

Si no está interesado en agregar una nueva biblioteca, deberá implementar más o menos manualmente el acceso de miembro o el operator[] .

Si tengo una clase de tensor simple como esta

struct Tensor { double XX, XY, XZ; double YX, YY, YZ; double ZX, ZY, ZZ; }

¿Es un comportamiento indefinido usar puntero-aritmética (ver más abajo) para acceder a sus elementos?

double& Tensor::operator[](int i) { assert(i < 9); return (&XX)[i]; }


Sí, es un comportamiento indefinido.

Los miembros de datos no están en una matriz y, por lo tanto, NO se garantiza que se almacenen consecutivamente en la memoria contigua, como requeriría la aritmética del puntero. Puede haber relleno indeterminado generado entre ellos.

La forma correcta sería acceder a los miembros individualmente, por ejemplo:

double& Tensor::operator[](int i) { switch (i) { case 0: return XX; case 1: return XY; case 2: return XZ; case 3: return YX; case 4: return YY; case 5: return YZ; case 6: return ZX; case 7: return ZY; case 8: return ZZ; default: throw std::out_of_range("invalid index"); } }

Alternativamente, si realmente desea utilizar la sintaxis de matriz:

double& Tensor::operator[](int i) { if ((i < 0) || (i > 8)) throw std::out_of_range("invalid index"); double* arr[] = { &XX, &XY, &XZ, &YX, &YY, &YZ, &ZX, &ZY, &ZZ }; return *(arr[i]); }

O

double& Tensor::operator[](int i) { if ((i < 0) || (i > 8)) throw std::out_of_range("invalid index"); static double Tensor::* arr[] = { &Tensor::XX, &Tensor::XY, &Tensor::XZ, &Tensor::YX, &Tensor::YY, &Tensor::YZ, &Tensor::ZX, &Tensor::ZY, &Tensor::ZZ }; return this->*(arr[i]); }

De lo contrario, use una matriz real para los datos y defina métodos para acceder a los elementos:

struct Tensor { double data[9]; double& XX() { return data[0]; } double& XY() { return data[1]; } double& XZ() { return data[2]; } double& YX() { return data[3]; } double& YY() { return data[4]; } double& YZ() { return data[5]; } double& ZX() { return data[6]; } double& ZY() { return data[7]; } double& ZZ() { return data[8]; } double& operator[](int i) { if ((i < 0) || (i > 8)) throw std::out_of_range("invalid index"); return data[i]; } };


Solo otra solución posible: envolver referencias en una clase y tener una matriz de envoltorios.

struct Tensor { double XX, XY, XZ; double YX, YY, YZ; double ZX, ZY, ZZ; class DoubleRefenceWrapper { double & ref; public: DoubleRefenceWrapper(double & r) : ref(r) {} double & get() { return ref; } } elements[9]; Tensor() : elements{XX, XY, XZ, YX, YY, YZ, ZX, ZY, ZZ} {} double& operator[](unsigned int i) { if(i < 9) { return elements[i].get(); } throw std::out_of_range("Tensor index must be in [0-8] range"); } };


¡Hay una charla de cppcon que menciona esto!

Entonces sí, es un comportamiento indefinido, porque las clases y las matrices no comparten una secuencia inicial común.

Editar: Miro Knejp presenta esa diapositiva alrededor de las 3:44 si desea más contexto para todos los que no son C ++ en la diapositiva, pero la pregunta y la respuesta es realmente la única parte de la charla que entra en su pregunta.