遍历C中的列表时是否看到优化错误?

Tom*_*ury 3 c gcc compiler-optimization compiler-bug

我一直在努力将同事的软件库集成到我们的大型应用程序中。他一直在写作和下测试他的图书馆-O0gcc 4.9.3。这是用于警报系统的嵌入式软件。该错误是在-Os优化条件下观察到的,我们也在上使用了直接C语言(没有C ++废话)gcc 4.9.3。该体系结构是ARM Cortex-M4F。

我在将该代码集成到较大的堆栈中时遇到问题。以下代码最多应进行迭代,GLOBAL_MAX_DEVICES直到在表中找到可用空间以插入其条目为止:

RET_VALUES DEVICE_Add_To_New_Table( uint8_t *new_id, uint16_t unique_id )
{
    RET_VALUES ret_value = RET_OK;
    uint8_t pos = 0;

[...]
    else
    {
        debug_print( "A,", true, DEBUG_DEVELOP_MODE_PLAIN, DEBUG_TRACE );

        // See if the unique_id is already in the table (relearn)
        while(( unique_id != DEVICE_new_table.unique_id[pos] ) && ( pos < GLOBAL_MAX_DEVICES ))
        {
            debug_print( "B,", false, DEBUG_DEVELOP_MODE_PLAIN, DEBUG_TRACE );
            pos++;
        }
[...]
Run Code Online (Sandbox Code Playgroud)

我们遇到的问题是,它不是循环迭代GLOBAL_MAX_DEVICES次数(当前为13),而是迭代200多次。序列“ B”在while(( unique_id != DEVICE_new_table.unique_id[pos] ) && ( pos < GLOBAL_MAX_DEVICES )) 循环中多次打印出来。

可以进行三种更改来解决此问题;

  • pos 可以将其设置为易失性,这(我认为)会抑制此值的某些优化。

  • while块中的参数可以颠倒,强制首先进行评估pos < GLOBAL_MAX_DEVICES

  • 可以添加一个构建标志-fno-aggressive-loop-optimizations,以关闭某些循环优化。

它似乎仅需要一个给定的修复程序即可解决此问题。

最初,我认为问题可能归结于短路优化。但是,我可以确认该unique_id参数不为零(在此示例中为0x042b),并且DEVICE_new_tableunique_id该结构的字段中将其初始化为全零。而且,在任何情况下,即使第一个参数为true,也应评估后者。短路仅应在第一个为假的情况下才适用,这显然不是这种情况,至少在最初不是这样,因为循环要迭代一段时间。

因此,我对为什么该循环重复数百次感到困惑(我认为最终会与内存地址发生冲突,这会导致与unique_id的机会匹配,可能是堆栈中的值。)在循环中添加了一条print语句打印迭代器pos会导致循环运行13,000次以上。13,000比13大得多,我已经检查了好几次。

比较由损坏的样本(添加了附加的printf语句)和工作样本(也与printf)产生的机器代码,表明工作变体(布尔运算符中的参数取反)包括与最大迭代次数的比较13

破碎:

        debug_print( "A,", true, DEBUG_DEVELOP_MODE_PLAIN, DEBUG_TRACE );
    ea7a:   4822        ldr r0, [pc, #136]  ; (eb04 <DEVICE_Add_To_New_Table+0xb0>)
    ea7c:   f8df 80a4   ldr.w   r8, [pc, #164]  ; eb24 <DEVICE_Add_To_New_Table+0xd0>
    ea80:   2101        movs    r1, #1
    ea82:   2200        movs    r2, #0
    ea84:   2306        movs    r3, #6
    ea86:   f00b fef9   bl  1a87c <debug_print>

        // See if the unique_id is already in the table (relearn)
        while(( unique_id != DEVICE_new_table.unique_id[pos] ) && ( pos < GLOBAL_MAX_DEVICES ))
    ea8a:   2400        movs    r4, #0
    ea8c:   f838 3f02   ldrh.w  r3, [r8, #2]!
    ea90:   454b        cmp r3, r9
    ea92:   b2e7        uxtb    r7, r4
    ea94:   4626        mov r6, r4
    ea96:   f104 0401   add.w   r4, r4, #1
    ea9a:   d00c        beq.n   eab6 <DEVICE_Add_To_New_Table+0x62>
        {
            uart_printf(-1, "%d,", pos);
    ea9c:   4632        mov r2, r6
    ea9e:   491a        ldr r1, [pc, #104]  ; (eb08 <DEVICE_Add_To_New_Table+0xb4>)
    eaa0:   f04f 30ff   mov.w   r0, #4294967295
    eaa4:   f7f8 ff6c   bl  7980 <uart_printf>
            debug_print( "B,", false, DEBUG_DEVELOP_MODE_PLAIN, DEBUG_TRACE );
    eaa8:   2100        movs    r1, #0
    eaaa:   4818        ldr r0, [pc, #96]   ; (eb0c <DEVICE_Add_To_New_Table+0xb8>)
    eaac:   460a        mov r2, r1
    eaae:   2306        movs    r3, #6
    eab0:   f00b fee4   bl  1a87c <debug_print>
    eab4:   e7ea        b.n ea8c <DEVICE_Add_To_New_Table+0x38>
            pos++;
        }
Run Code Online (Sandbox Code Playgroud)

工作(修剪掉不必要的部分),请注意损坏的样本中缺少的cmp r4, #13语句eab2

        debug_print( "A,", true, DEBUG_DEVELOP_MODE_PLAIN, DEBUG_TRACE );
    ea7a:   4845        ldr r0, [pc, #276]  ; (eb90 <DEVICE_Add_To_New_Table+0x13c>)
    ea7c:   f8df 8148   ldr.w   r8, [pc, #328]  ; ebc8 <DEVICE_Add_To_New_Table+0x174>
    ea80:   2101        movs    r1, #1
    ea82:   2200        movs    r2, #0
    ea84:   2306        movs    r3, #6
    ea86:   f00b ff4d   bl  1a924 <debug_print>
    ea8a:   4647        mov r7, r8
    ea8c:   2400        movs    r4, #0

        // See if the unique_id is already in the table (relearn)
        while(( pos < GLOBAL_MAX_DEVICES ) && ( unique_id != DEVICE_new_table.unique_id[pos] ))
    ea8e:   f837 3f02   ldrh.w  r3, [r7, #2]!
    ea92:   454b        cmp r3, r9
    ea94:   b2e5        uxtb    r5, r4
    ea96:   d00f        beq.n   eab8 <DEVICE_Add_To_New_Table+0x64>
        {
            uart_printf(-1, "%d,", pos);
    ea98:   4622        mov r2, r4
    ea9a:   493e        ldr r1, [pc, #248]  ; (eb94 <DEVICE_Add_To_New_Table+0x140>)
    ea9c:   f04f 30ff   mov.w   r0, #4294967295
    eaa0:   f7f8 ff6e   bl  7980 <uart_printf>
            debug_print( "B,", false, DEBUG_DEVELOP_MODE_PLAIN, DEBUG_TRACE );
    eaa4:   2100        movs    r1, #0
    eaa6:   483c        ldr r0, [pc, #240]  ; (eb98 <DEVICE_Add_To_New_Table+0x144>)
    eaa8:   460a        mov r2, r1
    eaaa:   2306        movs    r3, #6
    eaac:   3401        adds    r4, #1
    eaae:   f00b ff39   bl  1a924 <debug_print>

        // See if the unique_id is already in the table (relearn)
        while(( pos < GLOBAL_MAX_DEVICES ) && ( unique_id != DEVICE_new_table.unique_id[pos] ))
    eab2:   2c0d        cmp r4, #13
    eab4:   d1eb        bne.n   ea8e <DEVICE_Add_To_New_Table+0x3a>
    eab6:   4625        mov r5, r4
        {
            uart_printf(-1, "%d,", pos);
            debug_print( "B,", false, DEBUG_DEVELOP_MODE_PLAIN, DEBUG_TRACE );
            pos++;
        }
Run Code Online (Sandbox Code Playgroud)

我感觉像是程序员责怪他的工具承认失败,但是我真的看不到除了编译器错误以外还有什么。如果有人有任何想法,我将不胜感激。

Lun*_*din 5

如注释中所示,这是程序中的错误,即对数组的简单越界访问。

给定GLOBAL_MAX_DEVICES= 13,那么您最终将DEVICE_new_table.unique_id[13]无法访问。在调试版本中,您可能在那里有一个零,并且偶然“起作用”。然后,在释放状态下,您将获得一些非零值,并且回路陷入混乱,使回路终止条件短路。

另外,如果您的代码包含未定义的行为(例如越界访问),则当优化器尝试进行循环展开之类的操作时,您可能会生成真正奇怪的机器代码。例如,编译器可能会这样解释:“好的,因为数组中的所有项目均为零,所以此第一检查始终为true,因此我们可以在&&之后完全删除该检查”。

这可以通过以更简单的方式编写循环来解决。假设您希望保留posloop(?)之后的值,然后执行以下操作:

uint8_t pos = 0;
...
for(; pos<GLOBAL_MAX_DEVICES; pos++)
{
  if(unique_id != DEVICE_new_table.unique_id[pos])
    break;
  debug_print(...);
}
Run Code Online (Sandbox Code Playgroud)