c - 内存分配优化

标签 c gcc assembly optimization memory-barriers

众所周知,编译器可以混合赋值顺序以优化执行, 所以-

a=b;
c=d;

可以实际执行

c=d;
a=b;

但是使用以下代码:

a=b;
x=a;
func(x);

调用func(x)时,x必须首先包含b,否则结果可能不可预测。

现在,下面的代码怎么样:

int *addr1 = some_addr;
int *addr2 = (int *)0xf00;

/* The following applies:
 *      some_other_addr >= some_addr
 */
for (addr1; addr1 < some_other_addr; addr1++) 
{
    *addr1 += 1;
}

*addr2 *= 8;

addr2指向for循环范围内的地址时,我们需要知道是否 promise *addr2 将在 8 的乘法之前递增,就好像没有一样,并且一些优化步骤将 *addr2 *= 8; 放置在 for 循环之前, 的结果*addr2 将与未经优化执行时有所不同。

如果 some_addrsome_other_addr 在范围内定义并且它们作为参数传递,答案会有所不同吗?因为在第一种情况下,编译器很容易知道 *addr2 位于 for 循环的范围内,而在第二种情况下,情况就不那么明显了。

而且,如果我们从程序集角度来看,我们以 bss 部分初始化的示例 reset_handler 代码片段为例:

    ldr  r1, =__BSS_SIZE__
    cmp  r1, #0
    beq  FINISHED_LABEL
    ldr  r0, =__BSS_START__
    ldr  r2, =0
LOOP_LABEL:
    str  r2, [r0]
    add  r0, r4
    subs r1, r4
    bne  LOOP_LABEL

如果此代码之后的下一条指令(位于FINISHED_LABEL)从bss范围内的地址加载一个值(ldr),是否 promise 内容将有效(0) 当时?

最佳答案

编译器必须做的事情才能做到这一点,称为 "alias analysis" .

如果编译器可以证明 addr2 不在 addr1 循环的范围内,它可以对其重新排序或保留 *addr2在整个循环中的寄存器中。

对于像 for(...; addr1++) { *addr1 += *addr2; 这样的情况,这是一个非常有用的优化。 } 以避免每次都重新加载 addr2,原因之一 the restrict keyword存在。

如果输入可能重叠,编译器可以(并且确实)发出检查重叠的代码并运行优化的(例如自动向量化) 如果没有重叠则循环,或者如果有重叠则运行安全循环。


如果编译器无法证明转换将给出与 C 抽象机相同的最终结果,则它无法执行转换。 (我说“最终”是因为存储到内存的顺序不是可观察结果的一部分,除非您使用 std::atomic 。因此不允许编译时转换破坏 单线程代码,与乱序CPU的做法非常相似:为单个线程提供一切都按程序顺序发生的错觉。)

as-if 规则仅允许在不会导致 UB 的所有情况下进行优化,包括像 unsigned size = 0xffffffff,这通常会导致编译器无法进行您希望的优化,除非您调整源代码。

UB 是允许某些优化的关键(例如不在循环内重做带符号数组索引的符号扩展)。请参阅 What Every C Programmer Should Know About Undefined Behavior #1/3 .

关于c - 内存分配优化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49275474/

相关文章:

c - 缓冲区溢出攻击分段故障(核心转储)

assembly - 使用循环将十进制转换为二进制并显示它 - 汇编语言 EMU 8086

c - ELF 二进制文件中默认信号处理程序的代码在哪里?

c - scanf() 调用后的指令被调用两次

C 中的 Crypto++ 等价物

python - 命令 'gcc' 失败,退出状态 1(安装 psyco)

循环链表开头删除

gcc - ARM 编译错误,VFP 寄存器由可执行文件使用,而不是目标文件

c - gcc 警告 : assignment makes pointer from integer without cast 的未知原因

assembly - 为什么汇编程序有单独的段?