4

我们最近发现了一行代码相当于

bool should_escape_control_char(char ch) {
    return (ch < 0x20);  // control chars are 0x00 through 0x1F
}

如果 plainchar是 unsigned ,则此方法有效;但是如果 plainchar被签名,那么这个过滤器也会意外地捕获负字符。(最终的效果是一个朴素的 JSON 编码器正在编码"é""\u00c3\u00a9"因为对于编码器来说,它看起来像一对负字符,然后单独编码。)

IMO,这里的原罪是我们将一个普通char表达式与一个整数进行比较,结果取决于char. 我希望编译器告诉我们:

fantasy-warning: this comparison's result may depend on the signedness of plain char
    return (ch < 0x20);  // control chars are 0x00 through 0x1F
            ^~~~~~~~~
fantasy-note: cast the operand to silence this diagnostic
    return (ch < 0x20);  // control chars are 0x00 through 0x1F
            ~~
            (signed char)(ch)

我惊讶地发现在这种情况下 Clang 没有提供警告选项。而且我在 GCC 中也没有看到任何警告选项。

  • 我只是没有找对地方吗?
  • 在这种情况下,存在哪些工具/短绒/静态分析器发出警告?
4

2 回答 2

4

您的代码不可移植,即使您将其更改为

bool should_escape_control_char(unsigned char ch)

因为您仍在对平台上的字符编码进行假设。利用

int std::iscntrl( int ch );

相反,或 C 等效项,具体取决于您使用的语言。

参考https://en.cppreference.com/w/cpp/string/byte/iscntrl

(可从该站点访问 C 版本)。

于 2021-02-19T16:14:55.887 回答
1

我使用的静态分析器没有诊断出原始示例。编写单元测试并使用 unsigned 和 signed char 编译它们可以帮助在自动化测试阶段捕获此类错误。


使用无符号数时,将它们与显式无符号操作数进行比较比隐式转换有符号操作数更安全。因此,假设 char 是无符号的:

bool should_escape_control_char(char ch) {
    return ch < 0x20u;  // control chars are 0x00 through 0x1F
//                  ^
}

在这种情况下,如果假设的 char 签名错误,(至少有一些?)编译器会在 char 被签名并启用警告时发出警告:

warning: comparison of integer expressions of different signedness: 'char' and 'unsigned int' [-Wsign-compare]

与其依赖幻数,不如std::iscntrl从标准库中使用:

bool
is_control_c0(unsigned char ch) {
    return std::iscntrl(ch
        // provide locale if not using currently active
    );
}

请注意,接受单个窄字符(即代码单元)的函数无法匹配 UTF-8 中的所有控制代码点,因为 C1 控制代码被编码为两个代码单元。

于 2021-02-19T16:23:27.930 回答