记一次运算符优先级导致的 ROS 服务执行逻辑不符预期的问题

按:这文章我自己写出来都想笑。绕了一大圈发现错误无比简单。

背景

几天前,我一位室友 A 君在做学校某竞赛小组的入组考核项目。此项目其中一个子任务的要求如下:

  • 有一个服务端 Ser;

  • 有两个客户端 Cli1,Cli2;

  • Cli1 和 Cli2 会各自向 Ser 发送一个数字,但是发送顺序未知;

  • Ser 需要将两数的加和返回给 Cli1 和Cli2。

设计上采用了状态机:共计三个状态,Send、Polling 和 Fetched。对于客户端 CliX,默认处于 Send 状态,发送数字给 Ser 后进入 Polling 状态轮询计算结果,服务端会返回一个新的状态——如果计算好了就是 Fetched 附带结果,否则还是 Polling。

问题发生

服务端用一个计数器来维护目前获得的数字数量。计数器不会重置,只会对 2 求余判断是不是两个数字都被获取了。如果是,就会重置加和值。

这部分我帮 A 君做了,然后求余的地方故意装了个小X用的按位和。结果跑起来发现逻辑不对:那个 if branch 根本没有进去,里面的重置加和值的逻辑也没有执行,这是为什么?

可以看一下伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int fetchCounter = 0;

int sum = 0;
int sumCounter = 0;


bool callBack(Request& req, Response& resp) {
if (req.clientState == State::SEND) {
sum += req.num;
sumCounter ++;
resp.newState = State::POLLING;
}
if (req.clientState == State::POLLING) {
if (sumCounter % 2 == 0) {
resp.newState = State::FETCHED;
resp.sum = sum;
} else {
resp.newState = State::POLLING;
}
}
if (req.clientState == State::FETCHED) {
fetchCounter ++;
resp.newState = State::SEND;

/* 从这个地方…… */
if (fetchCounter & 1 == 0) {
sum = 0;
}
/* ……到这个地方都没执行 */
}
return true;
}

是啊,所以到底为什么呢?

排错

首先打了几个 ROS_INFO 进去,进行比较没慧根的调试。发现那个 if 分支确实无论如何都不会被执行。所以我怀疑这一整个 if 都被优化掉了,打开 Compiler Explorer 看看吧。

上述问题代码可以写成以下简化形式:

1
2
3
4
5
6
7
8
9
int a = 0; 
int b;

int main () {
if (a & 1 == 0) {
b = 0;
}
return 0;
}

使用 gcc 14.2 生成得到以下汇编代码:

1
2
3
4
5
6
7
8
9
10
a:
.zero 4
b:
.zero 4
main:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
popq %rbp
ret

可以看到 if 分支确实没了。那我缺的逻辑这块谁给我补啊?

一开始我怀疑是因为计数器默认是 0 然后编译器进行静态优化了,而且 callBack 函数基本上是放在 the middle of nowhere,可能编译器误判了,以为计数器不会被更新?那这也太夸张了吧。

那传统派呢?

1
2
3
4
5
6
7
8
9
int a = 0; 
int b;

int main () {
if (a % 2 == 0) {
b = 2;
}
return 0;
}

得到以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a:
.zero 4
b:
.zero 4
main:
pushq %rbp
movq %rsp, %rbp
movl a(%rip), %eax
andl $1, %eax
testl %eax, %eax
jne .L2
movl $2, b(%rip)
.L2:
movl $0, %eax
popq %rbp
ret

怎么又正常了?莫非对位运算和对求模的优化策略还不一样?

结果最后是什么原因呢……

1
2
3
4
5
6
7
8
9
int a = 0; 
int b;

int main () {
if ((a & 1) == 0) {
b = 2;
}
return 0;
}

此代码得到结果正常。

居然是沟槽的运算符优先级……首先这个 == 0 就比较挫,用一个 ! 解决的事情,不知道当时为什么这么写了,错误还很隐蔽……

1 == 0 先结合了得到 0,然后 a & 0 恒等于 0,编译器自然是要优化掉这块的啦……

总结

出这种问题确实有点笨蛋了。不过运算符优先级这种事情我之前确实没太深究过。真的会有人为了考试去嗯背那个吗?


记一次运算符优先级导致的 ROS 服务执行逻辑不符预期的问题
https://lizi.moe/2025/02/20/记一次-ROS-服务执行逻辑不符预期的问题/
作者
李萌
发布于
2025年2月20日
许可协议