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

时间:2019-06-03 12:30:05

标签: c gcc compiler-optimization compiler-bug

我一直在努力将同事的软件库集成到我们更大的应用程序中。他一直在-O0的{​​{1}}下编写和测试他的库。这是用于警报系统的嵌入式软件。在gcc 4.9.3优化下可以观察到此错误,并且我们在-Os上也使用了纯C(没有C ++废话)。该体系结构是ARM Cortex-M4F。

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

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++; } [...] 次(当前是13次),而是迭代200次以上。序列“ B”在GLOBAL_MAX_DEVICES循环中多次打印。

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

    可以使
  • while(( unique_id != DEVICE_new_table.unique_id[pos] ) && ( pos < GLOBAL_MAX_DEVICES ))为易失性,(我认为)这会抑制对该值的一些优化。

  • pos块中的参数可以颠倒,从而迫使对while的评估首先发生。

  • 可以添加一个构建标志pos < GLOBAL_MAX_DEVICES,以关闭某些循环优化。

它仅需解决一个问题即可解决。

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

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

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

损坏:

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++; } 处的cmp r4, #13语句,该语句在损坏的示例中不存在:

eab2

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

1 个答案:

答案 0 :(得分:5)

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

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

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

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

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