gcc优化全局变量使用的规则是什么?[duplicate]

dxxyhpgq  于 2022-11-12  发布在  其他
关注(0)|答案(1)|浏览(199)

此问题在此处已有答案

Once more volatile: necessary to prevent optimization?(3个答案)
16天前关闭。
我使用gcc编译了一个简单的ARM Cortex-M4测试代码,它会优化全局变量的使用,这让我很困惑。gcc优化全局变量使用的规则是什么?
GCC编译器:gcc-臂-无-eabi-8-2019-第3季度-更新/bin/臂-无-eabi-gcc
优化级别:- 奥斯
我的测试代码:
以下代码位于“foo.c”中,并且在任务A中调用了函数foo 1()和foo 2(),在任务B中调用了函数global_cnt_add()。

int g_global_cnt = 0;

void dummy_func(void);

void global_cnt_add(void)
{
    g_global_cnt++;
}

int foo1(void)
{
    while (g_global_cnt == 0) {
        // do nothing
    }

    return 0;
}

int foo2(void)
{
    while (g_global_cnt == 0) {
        dummy_func();
    }

    return 0;
}

The function dummy_func() is implemented in bar.c as following:

void dummy_func(void)
{
    // do nothing
}

函数foo 1()的汇编代码如下所示:

int foo1(void)
{
    while (g_global_cnt == 0) {
  201218:   4b02        ldr r3, [pc, #8]    ; (201224 <foo1+0xc>)
  20121a:   681b        ldr r3, [r3, #0]
  20121c:   b903        cbnz    r3, 201220 <foo1+0x8>
  20121e:   e7fe        b.n 20121e <foo1+0x6>
        // do nothing
    }

    return 0;
}
  201220:   2000        movs    r0, #0
  201222:   4770        bx  lr
  201224:   00204290    .word   0x00204290

函数foo 2()的汇编代码如下所示:

int foo2(void)
{
  201228:   b510        push    {r4, lr}
    while (g_global_cnt == 0) {
  20122a:   4c04        ldr r4, [pc, #16]   ; (20123c <foo2+0x14>)
  20122c:   6823        ldr r3, [r4, #0]
  20122e:   b10b        cbz r3, 201234 <foo2+0xc>
        dummy_func();
    }

    return 0;
}
  201230:   2000        movs    r0, #0
  201232:   bd10        pop {r4, pc}
        dummy_func();
  201234:   f1ff fcb8   bl  400ba8 <dummy_func>
  201238:   e7f8        b.n 20122c <foo2+0x4>
  20123a:   bf00        nop
  20123c:   00204290    .word   0x00204290

在函数foo 1()的汇编代码中,全局变量“g_global_cnt”只被加载一次,while循环永远不会被打断,编译器优化了“g_global_cnt”的使用,我知道我可以添加volatile来避免这种优化。
在函数foo 2()的汇编代码中,在每个while循环中加载并检查全局变量“g_global_cnt”,可以中断while循环。
gcc的优化规则有哪些?

5tmbdcev

5tmbdcev1#

为了理解这种行为,你必须考虑副作用和序列点ref
对于编译器 ,副作用是指运算符、表达式、语句或函数的结果,即使在运算符、表达式、语句或函数完成求值后,副作用仍然存在。
而 * 序列点定义了计算机程序执行过程中的任何一个点,在这个点上,可以保证先前求值的所有副作用都已执行,并且还没有执行后续求值的任何副作用。*
序列点的主要规则是,除了计算变量值的变化之外,不会出于任何目的在两个点之间多次访问变量
引用C标准:
在抽象机器中,所有表达式都按照语义的规定求值。如果一个实际的实现可以推断出表达式的值没有被使用,并且没有产生所需的副作用(包括任何由调用函数或访问volatile对象引起的副作用),那么它就不需要对表达式的一部分求值。
在您的代码中

int foo1(void)
{
    while (g_global_cnt == 0) {
        // do nothing
    }

    return 0;
}

在阅读g_global_cnt之后,就不会有任何可能影响变量值的副作用了。编译器无法知道它是在函数作用域之外被修改的,因此它认为你只能读取它一次,这是因为函数作用域中没有更多的序列点。
告诉编译器每次读取都有副作用的方法是用标识符volatile标记变量。
对于int g_global_cnt = 0;

adrp    x0, g_global_cnt
        add     x0, x0, :lo12:g_global_cnt
        ldr     w0, [x0]
        cmp     w0, 0
        beq     .L3
        mov     w0, 0
        ret

对于volatile int g_global_cnt = 0;

adrp    x0, g_global_cnt
        add     x0, x0, :lo12:g_global_cnt
        ldr     w0, [x0]
        cmp     w0, 0
        cset    w0, eq
        and     w0, w0, 255
        cmp     w0, 0
        bne     .L3
        mov     w0, 0
        ret

相关问题