这可能会成为一个愚蠢的问题,但我真的不明白为什么
clang-tidy
在这里抱怨。
考虑以下配置:
# .clang-tidy
---
FormatStyle: file
WarningsAsErrors: '*'
Checks: >
-*,
hicpp-signed-bitwise,
CheckOptions:
hicpp-signed-bitwise.IgnorePositiveIntegerLiterals: true
以及以下最小示例:
#include "stdint.h"
#include "stdio.h"
#include "inttypes.h"
typedef uint8_t mytypeU8_t;
typedef uint32_t mytypeU32_t;
int main(void) {
mytypeU8_t mytypeU8 = 0;
mytypeU32_t mytypeU32 = 0;
unsigned char testChar = 0;
printf("%"PRIu8, testChar & 1);
printf("%"PRIu8, testChar << 2);
printf("%"PRIu8, (testChar << 2 ) | (testChar >> 2));
printf("%"PRIu8, mytypeU8 & 1);
printf("%"PRIu8, mytypeU8 << 2);
printf("%"PRIu8, (mytypeU8 << 2 ) | (mytypeU8 >> 2));
printf("%"PRIu32, mytypeU32 & 1);
printf("%"PRIu32, mytypeU32 << 2);
printf("%"PRIu32, (mytypeU32 << 2 ) | (mytypeU32 >> 2));
return 0;
}
为什么
clang-tidy
打印这些错误:
test.c:15:22: error: use of a signed integer operand with a binary bitwise operator [hicpp-signed-bitwise,-warnings-as-errors]
15 | printf("%"PRIu8, (testChar << 2 ) | (testChar >> 2));
| ^~~~~~~~~~~~~~~~ ~
test.c:19:22: error: use of a signed integer operand with a binary bitwise operator [hicpp-signed-bitwise,-warnings-as-errors]
19 | printf("%"PRIu8, (mytypeU8 << 2 ) | (mytypeU8 >> 2));
| ^~~~~~~~~~~~~~~~ ~
test.c:21:35: error: use of a signed integer operand with a binary bitwise operator [hicpp-signed-bitwise,-warnings-as-errors]
21 | printf("%"PRIu32, mytypeU32 & 1);
| ~ ^
编辑:
更多地考虑这一点,我认为前两个错误可能源于这样一个事实:8 位值在被
int
之前隐式提升为 |
,这是可以理解的。
但是为什么要抱怨
mytypeU32 & 1
而不是其他任何人 - mytypeU32 << 2
、testChar & 1
等?
hicpp-signed-bitwise
检查器实现
规则5.6.1
Perforce 发布的高完整性 C++ 编码标准:
5.6.1 不要对带符号操作数使用按位运算符
在某些情况下,将带符号操作数与按位运算符一起使用会受到未定义或实现定义的行为的影响。因此,按位运算符只能与无符号整数类型的操作数一起使用。
这太模糊了,无法回答问题的某些方面,所以我会集中精力 关于 Clang 的实现。
将
IgnorePositiveIntegerLiterals
设置为 true
(根据
问题中的 .clang-tidy
文件),hicpp-signed-bitwise
检查
(文档,
来源)
在以下情况下报告按位运算符的使用:
任一操作数匹配
SignedIntegerOperand
和
两个操作数都有整型,这意味着 类型::isIntegerType 返回
true
。
SignedIntegerOperand
(在此配置中)定义为
Clang AST 匹配器:
expr(ignoringImpCasts(hasType(isSignedInteger())),
unless(integerLiteral()))
这意味着操作数必须在跳过隐式转换后, 有符号整数类型 (Type::isSignedIntegerType 是
true
),
但不是整数文字。请注意,第二次检查确实
不跳过隐式转换。
(我忽略了一些与问题无关的其他检查器详细信息。)
我不知道为什么 Clang 实现者选择了特定的 解释他们所做的规则,但我们现在可以继续使用 它来分析示例。
我会一次一个地查看测试中的每个相关表达式,看看 为什么它被报道或未被报道。
首先,我会注意到所有示例都满足第二个条件, 因为两个操作数在每种情况下都具有整数类型。所以有趣的是 方面是
SignedIntegerOperand
匹配器,必须满足
由至少一个操作数来报告操作员。
testChar & 1 // not reported
此处,
testChar
隐式提升为 int
(C99 6.5.10 p3, 6.3.1.8
p1、6.3.1.1 p2)。但是当跳过隐式转换时,
testChar
未签名,因此不匹配。同时,1
没有
隐式转换,并且由于它是整数文字,因此也是如此
不匹配,因此没有报告。
testChar << 2 // not reported
类似地,
testChar
被提升(C99 6.5.7 p3),但在下面未签名
那;并且 2
未升级(它已经是 int
),并且是
字面意思。
(testChar << 2 ) | (testChar >> 2) // reported
按照上述逻辑不会报告移位表达式。但对于 按位或表达式,两个操作数都具有类型
int
,并且都不是
字面意思,所以被报道。
mytypeU8 << 2 // not reported
与
testChar << 2
相同。
(mytypeU8 << 2 ) | (mytypeU8 >> 2) // reported
与
(testChar << 2 ) | (testChar >> 2)
相同。
mytypeU32 & 1 // reported
这是第一个棘手的问题。
mytypeU32
是无符号的,所以不
匹配。但是 1
看起来像一个整数文字,所以报告应该是
压制了,对吧?但由于 LHS 是 unsigned int
,所以 RHS 是
也转换为unsigned int
!这增加了隐式转换,
并且该转换不是整数。
可以通过将匹配表达式更改为来确认这一点:
expr(ignoringImpCasts(hasType(isSignedInteger())),
unless(ignoringImpCasts(integerLiteral())))
// ^^^^^^^^^^^^^^^^
// inserted
在这种情况下,它将不再匹配,因此不会被报告。 (我 使用
clang-query
检查这一点,而不是实际修改
clang-tidy
,为了方便起见。)
mytypeU32 << 2 // not reported
这是第二个棘手的问题。这里,
mytypeU32
是无符号的,也是如此
不满足SignedIntegerOperand
。但是2
呢?难道不是
用于 mytypeU32 & 1
的逻辑适用吗?不——因为虽然 &
使用
“通常的算术转换”以达到common类型,<<
仅仅是
独立地提升两个操作数,因为类型不需要是
相同。因此, 2
已经具有类型 int
(因此不会
升级下的更改),是 not 隐式转换,因此 is
整数文字,因此导致 SignedIntegerOperand
也不会
匹配。
(mytypeU32 << 2 ) | (mytypeU32 >> 2) // not reported
两个操作数均无符号,因此
SignedIntegerOperand
不匹配。
问题 “使用带符号整数操作数和二进制按位运算符” - 使用无符号短整型时 及其答案有一些有趣的相关讨论,包括 该规则的基本原理以及 Clang 对其的解释。