我正在尝试重新实现 malloc 并且我需要了解对齐的目的。据我了解,如果内存对齐,代码将执行得更快,因为处理器不必采取额外的步骤来恢复被切割的内存位。我想我明白 64 位处理器读取 64 位 x 64 位内存。现在,让我们假设我有一个按顺序排列的结构(没有填充):一个字符、一个短字符、一个字符和一个整数。为什么短路会错位?我们拥有区块中的所有数据!为什么它必须在一个 2 的倍数的地址上。对于整数和其他类型,同样的问题?
我还有第二个问题:使用我之前提到的结构,处理器如何知道当它读取它的 64 位时前 8 位对应于一个字符,然后接下来的 16 位对应于一个短等等......?
这就是我的意思:i++ + i++未定义,对数组的越界写入也是如此.
超出范围的数组写入的不确定性是可以理解的:它可能被利用来运行任意代码,这是您可以获得的未定义代码.让我们调用这个运行时未定义的行为.
对于i++ + i++,然而,故事似乎是不同的.假设编译器生成了一些东西.它究竟是什么未定义的.非常不确定.事实上,它是如此不确定,我们曾经听过猫可能怀孕(虽然最近 - 从CppCon 2016,我认为 - 人们开始意识到,未定义的行为毕竟不能让猫怀孕).
但是,一旦我们打开盒子,看看编译器生成了什么,并且它不是可利用的代码(注入,数据竞争等等 - 例如,编译器选择i++ + i++完全抛弃),是不是它将完全被执行 -是不是从那一点上完美定义了?
换句话说,最后一种情况是我们可以称之为编译时未定义的行为.用猫来说,它类似于薛定谔的猫,在你打开盒子之前它的状态是未知的(参见生成的装配),此时你会看到要执行的实际现实.(我想知道未定义的行为是否会使有毒的死猫怀孕.)
当然,未定义的行为是适用于该标准的法律术语.问题是关于现实中发生的"行为".
我__int128用作struct的成员.它可以找到-O0(没有优化).
但是,如果启用了优化(-O1),则会因段故障而崩溃.
它在指令处崩溃movdqa,需要将var对齐16,而地址的分配malloc()仅由8对齐.
我尝试禁用SSE优化-mno-sse,但无法编译:
/usr/include/x86_64-linux-gnu/bits/stdlib-float.h:27:1: error: SSE register return with SSE disabled
Run Code Online (Sandbox Code Playgroud)
所以,我能做些什么,如果我想使用__int128和-O1两者兼而有之?
在此先感谢吴
顺便说一下,如果__int128仅在堆栈上使用(不在堆上),似乎没问题.
====编辑====
对不起,我没说实话.
其实我没用过malloc().我使用了一个内存池lib,它返回8对齐的地址.我说malloc()只是为了让事情变得简单.
经过测试,我已经知道它malloc()与16对齐.并且__int128成员也在结构中对齐16.
所以问题是我的内存池lib.
非常感谢.
这是一个后续问题
有很多文章和博客提到 Java 和 JVM 指令重新排序,这可能会导致用户操作中出现反直觉的结果。
当我要求演示 Java 指令重新排序导致意外结果时,有几条评论说,更普遍的关注领域是内存重新排序,并且很难在 x86 CPU 上进行演示。
指令重新排序只是内存重新排序、编译器优化和内存模型等更大问题的一部分吗?这些问题真的是 Java 编译器和 JVM 特有的吗?它们是否特定于某些 CPU 类型?
java cpu-architecture memory-barriers instruction-reordering
我们假设有一个 32 位 CPU 和 2 字节short。我知道 4 字节int需要在 4 的倍数地址处对齐,以避免额外读取。
问题:
0x1读取。0x0那么,为什么 Shorts 需要以 2 的倍数地址对齐呢?0x2,为什么它会被认为是对齐和高效的,因为CPU只能从0x0读取并丢弃前两个字节?有一个问题与此非常相似,但是,答案仅告诉我们short结构体和独立变量中的对齐要求是相同的。还有一条获得 2 票赞成的评论说:
在许多机器上,当数量是 N 字节对齐时,访问 N 字节数量(至少是 {1, 2, 4, 8, 16} 中的 N)的效率最高。生活就是这样;习惯它,因为我怀疑芯片制造商会仅仅因为你认为它不应该是这样而改变它。
但为什么?
是否有一个内在函数可以在输入数组中的所有位置设置单个值,其中相应位置在提供的 BitMask 中具有 1 位?
10101010 是位掩码
值为 121
它将设置位置 0,2,4,6 值为 121
我有这个代码:
double a[bufferSize];
double b[voiceSize][bufferSize];
double c[voiceSize][bufferSize];
...
inline void AddIntrinsics(int voiceIndex, int blockSize) {
// assuming blockSize / 2 == 0 and voiceIndex is within the range
int iters = blockSize / 2;
__m128d *pA = (__m128d*)a;
__m128d *pB = (__m128d*)b[voiceIndex];
double *pC = c[voiceIndex];
for (int i = 0; i < iters; i++, pA++, pB++, pC += 2) {
_mm_store_pd(pC, _mm_add_pd(*pA, *pB));
}
}
Run Code Online (Sandbox Code Playgroud)
但是,"有时",它提高访问内存冲突,我认为它是由于缺少我的3个数组的内存对齐的a,b和c.
但是,由于我操作__m128d(使用__declspec(align(16))),当我转换为指针时,是否保证对齐? …
鉴于 x86 具有强大的内存模型,是否有必要使用std::memory_order_acquirefence(不是 operation)?
例如,如果我有这个代码:
uint32_t read_shm(const uint64_t offset) {
// m_head_memory_location is char* pointing to the beginning of a mmap-ed named shared memory segment
// a different process on different core will write to it.
return *(uint32_t*)(m_head_memory_location + offset);
}
....
int main() {
uint32_t val = 0;
while (0 != (val = shm.read(some location)));
.... // use val
}
Run Code Online (Sandbox Code Playgroud)
我真的需要std::atomic_thread_fence(std::memory_order_acquire)在return语句之前吗?
我觉得没有必要,因为上面代码的目标是尝试从 读取前 4 个字节m_head_memory_location + offset,因此重新排序栅栏后的任何内存操作都不会影响结果。
或者有一些副作用使获取栅栏变得必要?
在 x86 上是否需要获取栅栏(不是操作)? …
我在某些计算带有整数操作数的浮点表达式的代码中遇到了 HardFault 异常。操作数按地址传递,然后将其转换(隐式或显式转换)为浮点数。当操作数不是 32 位对齐时(这不在我的控制之下),我得到异常。
我试图在这里重现 Godbolt 上的行为,生成的代码与我在设备上得到的一致。
基本上,下面的反汇编代码
vldr.32 s0, [r0] @ int
Run Code Online (Sandbox Code Playgroud)
在vldr需要对齐地址的指令中直接使用传递给函数的可能未对齐的地址。
我发现这个问题解决了类似的问题,但他们在那里谈论浮点指针。在这种情况下,我知道浮动不能未对齐。
在我的情况下,我正在处理整数,允许未对齐,但编译器假定它仍然可以使用 vldr 指令中的地址。更让我困惑的是这段代码
uint32_t pippo = *(uint32_t *)src;
float pippof = pippo * 10.0f;
Run Code Online (Sandbox Code Playgroud)
当提供未对齐的地址时,可能会或可能不会产生异常,这取决于优化级别,因为-O0例如在堆栈上分配了一个整数。
所以我的问题是: