以类似方式访问数组中的结构成员:在不同于数组的结构中填充?
accessing struct members in array like manner: padding in structs different than in arrays?
假设我有一个包含多个成员的结构,每个成员都属于同一类型。我想通过名称和数组索引安全地访问成员,所以我引入了一个 union
,它包含实际的 struct
和一个数组,它对应于 [=13 的成员=] 在数量和类型方面。请参阅以下代码,它说明了我如何通过使用这样的 union
:
以两种不同的方式 "view" struct
#include <iostream>
template<typename elemT>
struct productData {
elemT color;
elemT size;
elemT application;
elemT division;
};
template<typename elemT>
union productDataAsArray {
struct productData<elemT> p;
elemT arr[(sizeof(productData<elemT>) / sizeof(elemT))];
};
int main() {
union productDataAsArray<int> myProduct;
myProduct.p.color = 10;
myProduct.p.size = 20;
myProduct.p.application = 30;
myProduct.p.division = 40;
for (int i=0; i<4; i++) {
std::cout << "elem #" << i << ":" << myProduct.arr[i] << std::endl;
}
}
标准假设数组的内存布局对应于struct
的内存布局是否安全/保证,这样两种不同的访问方法对于任何类型都可以达到预期的结果elemT
?
以下static_asserts
几种不同的类型表明它应该可以工作。但真的有保障吗?
static_assert(sizeof(productData<int>)==sizeof(productDataAsArray<int>::arr), "padding/alignment inconsistency");
static_assert(sizeof(productData<char>)==sizeof(productDataAsArray<char>::arr), "padding/alignment inconsistency");
static_assert(sizeof(productData<double>)==sizeof(productDataAsArray<double>::arr), "padding/alignment inconsistency");
static_assert(sizeof(productData<char*>)==sizeof(productDataAsArray<char*>::arr), "padding/alignment inconsistency");
class alignas(16) testClass {
int x;
int y;
virtual void test() {};
};
static_assert(sizeof(productData<testClass>)==sizeof(productDataAsArray<testClass>::arr), "padding/alignment inconsistency");
这里先不说内存布局了。
您已经通过读取 union
成员而不是最后分配给的成员来调用 UB。在标准C++中也不例外。
明确地说:您所有 "Is it safe/guaranteed" 问题的答案是 "No"。
But is it actually guaranteed?
没有
您通过访问 union
.
的非活动成员调用 未定义行为
您的方法是 UB,您可以改为执行以下操作:
template<typename elemT>
class productData {
elemT data[4];
public:
const elemT& operator[](int i) const { return data[i];}
const elemT& color() const { return data[0]; }
const elemT& size() const { return data[1]; }
const elemT& application() const { return data[2]; }
const elemT& division() const { return data[3]; }
elemT& operator[](int i) { return data[i];}
elemT& color() { return data[0]; }
elemT& size() { return data[1]; }
elemT& application() { return data[2]; }
elemT& division() { return data[3]; }
};
正如其他帖子所提到的,不能保证。
您的 static_assert
也不足以检查,因为您可以在不同的地方填充。您应该在静态断言中明确验证内存位置:
namespace {
const productDataAsArray<int> testObj{0};
static_assert(&testObj.p.color == &testObj.arr[0]);
static_assert(&testObj.p.size == &testObj.arr[1]);
static_assert(&testObj.p.application == &testObj.arr[2]);
static_assert(&testObj.p.division == &testObj.arr[3]);
}
更好的解决方案是使用枚举索引:
enum productDataIndexes {
PRODUCT_COLOR = 0,
PRODUCT_SIZE,
PRODUCT_APPLICATION,
PRODUCT_DIVISION,
NUM_PRODUCT_INDEXES
};
int main() {
...
myProduct[PRODUCT_COLOR] = 10;
myProduct[PRODUCT_SIZE] = 20;
myProduct[PRODUCT_APPLICATION] = 30;
myProduct[PRODUCT_DIVISION] = 40;
for (int i=0; i<NUM_PRODUCT_INDEXES; i++) {
std::cout << "elem #" << i << ":" << myProduct[i] << std::endl;
}
}
假设我有一个包含多个成员的结构,每个成员都属于同一类型。我想通过名称和数组索引安全地访问成员,所以我引入了一个 union
,它包含实际的 struct
和一个数组,它对应于 [=13 的成员=] 在数量和类型方面。请参阅以下代码,它说明了我如何通过使用这样的 union
:
struct
#include <iostream>
template<typename elemT>
struct productData {
elemT color;
elemT size;
elemT application;
elemT division;
};
template<typename elemT>
union productDataAsArray {
struct productData<elemT> p;
elemT arr[(sizeof(productData<elemT>) / sizeof(elemT))];
};
int main() {
union productDataAsArray<int> myProduct;
myProduct.p.color = 10;
myProduct.p.size = 20;
myProduct.p.application = 30;
myProduct.p.division = 40;
for (int i=0; i<4; i++) {
std::cout << "elem #" << i << ":" << myProduct.arr[i] << std::endl;
}
}
标准假设数组的内存布局对应于struct
的内存布局是否安全/保证,这样两种不同的访问方法对于任何类型都可以达到预期的结果elemT
?
以下static_asserts
几种不同的类型表明它应该可以工作。但真的有保障吗?
static_assert(sizeof(productData<int>)==sizeof(productDataAsArray<int>::arr), "padding/alignment inconsistency");
static_assert(sizeof(productData<char>)==sizeof(productDataAsArray<char>::arr), "padding/alignment inconsistency");
static_assert(sizeof(productData<double>)==sizeof(productDataAsArray<double>::arr), "padding/alignment inconsistency");
static_assert(sizeof(productData<char*>)==sizeof(productDataAsArray<char*>::arr), "padding/alignment inconsistency");
class alignas(16) testClass {
int x;
int y;
virtual void test() {};
};
static_assert(sizeof(productData<testClass>)==sizeof(productDataAsArray<testClass>::arr), "padding/alignment inconsistency");
这里先不说内存布局了。
您已经通过读取 union
成员而不是最后分配给的成员来调用 UB。在标准C++中也不例外。
明确地说:您所有 "Is it safe/guaranteed" 问题的答案是 "No"。
But is it actually guaranteed?
没有
您通过访问 union
.
您的方法是 UB,您可以改为执行以下操作:
template<typename elemT>
class productData {
elemT data[4];
public:
const elemT& operator[](int i) const { return data[i];}
const elemT& color() const { return data[0]; }
const elemT& size() const { return data[1]; }
const elemT& application() const { return data[2]; }
const elemT& division() const { return data[3]; }
elemT& operator[](int i) { return data[i];}
elemT& color() { return data[0]; }
elemT& size() { return data[1]; }
elemT& application() { return data[2]; }
elemT& division() { return data[3]; }
};
正如其他帖子所提到的,不能保证。
您的 static_assert
也不足以检查,因为您可以在不同的地方填充。您应该在静态断言中明确验证内存位置:
namespace {
const productDataAsArray<int> testObj{0};
static_assert(&testObj.p.color == &testObj.arr[0]);
static_assert(&testObj.p.size == &testObj.arr[1]);
static_assert(&testObj.p.application == &testObj.arr[2]);
static_assert(&testObj.p.division == &testObj.arr[3]);
}
更好的解决方案是使用枚举索引:
enum productDataIndexes {
PRODUCT_COLOR = 0,
PRODUCT_SIZE,
PRODUCT_APPLICATION,
PRODUCT_DIVISION,
NUM_PRODUCT_INDEXES
};
int main() {
...
myProduct[PRODUCT_COLOR] = 10;
myProduct[PRODUCT_SIZE] = 20;
myProduct[PRODUCT_APPLICATION] = 30;
myProduct[PRODUCT_DIVISION] = 40;
for (int i=0; i<NUM_PRODUCT_INDEXES; i++) {
std::cout << "elem #" << i << ":" << myProduct[i] << std::endl;
}
}