我一直在努力将同事的软件库集成到我们更大的应用程序中。他一直在-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
我觉得程序员在指责他的工具承认失败,但是我真的看不到除了编译器错误以外还有什么。如果有人有什么想法,我将不胜感激。
答案 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(...);
}