class Wrapper {
public:
// some functions operating on the value_
__m128i value_;
};
int main() {
std::vector<Wrapper> a;
a.resize(100);
}
value_
中Wrapper
个vector a
对象的__m128i values
属性是否总是占据连续的记忆而[128 bit for 1st Wrapper][no gap here][128bit for 2nd Wrapper] ...
之间没有任何差距?
我的意思是:
Wrapper
到目前为止,g ++和我使用的英特尔cpu以及gcc godbolt似乎都是如此。
由于#include <iostream>
#include <vector>
#include <x86intrin.h>
int main()
{
static constexpr size_t N = 1000;
std::vector<__m128i> a;
a.resize(1000);
//__m128i a[1000];
uint32_t* ptr_a = reinterpret_cast<uint32_t*>(a.data());
for (size_t i = 0; i < 4*N; ++i)
ptr_a[i] = i;
for (size_t i = 1; i < N; ++i){
a[i-1] = _mm_and_si128 (a[i], a[i-1]);
}
for (size_t i = 0; i < 4*N; ++i)
std::cout << ptr_a[i];
}
对象中只有一个__m128i属性,这是否意味着编译器总是不需要在内存中添加任何类型的填充? (Memory layout of vector of POD objects)
测试代码1:
warning: ignoring attributes on template argument
'__m128i {aka __vector(2) long long int}'
[-Wignored-attributes]
警告:
.L9:
add rax, 16
movdqa xmm1, XMMWORD PTR [rax]
pand xmm0, xmm1
movaps XMMWORD PTR [rax-16], xmm0
cmp rax, rdx
movdqa xmm0, xmm1
jne .L9
汇编(gcc god bolt):
pand
我想这意味着数据是连续的,因为循环只是将16个字节添加到它在循环的每个循环中读取的内存地址。它使用#include <iostream>
#include <vector>
#include <x86intrin.h>
class Wrapper {
public:
__m128i value_;
inline Wrapper& operator &= (const Wrapper& rhs)
{
value_ = _mm_and_si128(value_, rhs.value_);
}
}; // Wrapper
int main()
{
static constexpr size_t N = 1000;
std::vector<Wrapper> a;
a.resize(N);
//__m128i a[1000];
uint32_t* ptr_a = reinterpret_cast<uint32_t*>(a.data());
for (size_t i = 0; i < 4*N; ++i) ptr_a[i] = i;
for (size_t i = 1; i < N; ++i){
a[i-1] &=a[i];
//std::cout << ptr_a[i];
}
for (size_t i = 0; i < 4*N; ++i)
std::cout << ptr_a[i];
}
来执行按位和。
测试代码2:
.L9:
add rdx, 2
add rax, 32
movdqa xmm1, XMMWORD PTR [rax-16]
pand xmm0, xmm1
movaps XMMWORD PTR [rax-32], xmm0
movdqa xmm0, XMMWORD PTR [rax]
pand xmm1, xmm0
movaps XMMWORD PTR [rax-16], xmm1
cmp rdx, 999
jne .L9
大会(gcc god bolt)
rax
看起来也没有填充。 add rdx,2
每步增加32,即2 x 16.额外的#include <iostream>
#include <vector>
#include <x86intrin.h>
int main()
{
static constexpr size_t N = 1000;
std::vector<__m128i> a;
a.resize(1000);
//__m128i a[1000];
uint32_t* ptr_a = reinterpret_cast<uint32_t*>(a.data());
for (size_t i = 0; i < 4*N; ++i)
ptr_a[i] = i;
for (size_t i = 1; i < N; ++i){
a[i-1] = _mm_and_si128 (a[i], a[i-1]);
}
for (size_t i = 0; i < 4*N; ++i)
std::cout << ptr_a[i];
}
绝对不如测试代码1的循环好。
测试自动矢量化
.L21:
movdqu xmm0, XMMWORD PTR [r10+rax]
add rdi, 1
pand xmm0, XMMWORD PTR [r8+rax]
movaps XMMWORD PTR [r8+rax], xmm0
add rax, 16
cmp rsi, rdi
ja .L21
汇编(god bolt):
let data: Int = json["data"] as? Int ?? 0
...我只是不知道对于intel cpu和g ++ / intel c ++编译器/(在此处插入编译器名称)是否总是如此...
答案 0 :(得分:2)
不能保证在class Wrapper
的末尾不会有填充,只是在开头处没有填充。
根据C++11
标准:
9.2 班级成员 [ class.mem ]
20 指向标准布局结构对象的指针,使用reinterpret_cast进行适当转换,指向其初始成员(或者如果该成员是位字段,则指向它的单位)居住),反之亦然。 [注意:因此,在标准布局结构对象中可能存在未命名的填充,但不是在其开头,以实现适当的对齐。 - 结束说明]
也在sizeof
下:
5.3.3 尺寸 [ expr.sizeof ]
2 当应用于引用或引用类型时,结果是引用类型的大小。申请时 对于一个类,结果是该类对象中的字节数,包括所需的任何填充 将该类型的对象放在数组中。
答案 1 :(得分:1)
不保证。 Galik's answer引用了标准,因此我将重点关注假设它将是连续的一些风险。
我写了这个小程序并用gcc编译,它确实连续地整数:
#include <iostream>
#include <vector>
class A
{
public:
int a;
int method() { return 1;}
float method2() { return 5.5; }
};
int main()
{
std::vector<A> as;
for(int i = 0; i < 10; i++)
{
as.push_back(A());
}
for(int i = 0; i < 10; i++)
{
std::cout << &as[i] << std::endl;
}
}
然而,只有一个小小的变化,差距开始显现:
#include <iostream>
#include <vector>
class A
{
public:
int a;
int method() { return 1;}
float method2() { return 5.5; }
virtual double method3() { return 0.1; } //this is the only change
};
int main()
{
std::vector<A> as;
for(int i = 0; i < 10; i++)
{
as.push_back(A());
}
for(int i = 0; i < 10; i++)
{
std::cout << &as[i] << std::endl;
}
}
具有虚方法的对象(或从具有虚方法的对象继承的对象)需要存储一些额外的信息以知道在哪里找到适当的方法,因为它不知道基类或任何基类之间的哪个覆盖直到运行时。这就是为什么建议never use memset
on a class。正如其他答案所指出的那样,也可能存在填充,这不能保证在编译器甚至同一编译器的不同版本之间保持一致。
最后,假设它在给定的编译器上是连续的可能是不值得的,即使你测试它并且它工作,稍后添加虚拟方法之类的简单事情会让你头疼
答案 2 :(得分:1)
在实践中假设填充是安全的,除非您正在编译非标准ABI。
所有针对相同ABI的编译器必须对结构/类大小/布局做出相同的选择,并且所有标准ABI /调用约定在结构中都没有填充。 (即x86-32和x86-64 System V和Windows,请参阅x86标记wiki以获取链接)。您使用一个编译器的实验确认了针对同一平台/ ABI的所有编译器。
请注意,此问题的范围仅限于支持英特尔内在函数和__m128i
类型的x86编译器,这意味着我们拥有比仅使用ISO C ++标准所获得的更强大的保证。任何特定于实现的东西。
正如@zneak指出的那样,你可以在类def中static_assert(std::is_standard_layout<Wrapper>::value)
来提醒人们不要添加任何虚拟方法,这会为每个实例添加一个vtable指针。