使用 char 参数从 <cctype> 调用函数是否安全?

Rol*_*lig 7 c c++ character undefined-behavior language-lawyer

C 编程语言表示,这些函数<ctype.h>遵循一个共同的要求:

ISO C99,7.4p1:

在所有情况下,参数都是 an int,其值应表示为 anunsigned char或应等于宏的值EOF。如果参数有任何其他值,则行为未定义。

这意味着以下代码是不安全的:

int upper(const char *s, size_t index) {
  return toupper(s[index]);
}
Run Code Online (Sandbox Code Playgroud)

如果此代码在char具有相同值空间的实现上执行,signed char并且字符串中有一个具有负值的字符,则此代码调用未定义行为。正确的版本是:

int upper(const char *s, size_t index) {
  return toupper((unsigned char) s[index]);
}
Run Code Online (Sandbox Code Playgroud)

尽管如此,我还是在 C++ 中看到了许多不关心这种未定义行为可能性的例子。那么 C++ 标准中是否有任何内容可以保证上述代码不会导致未定义的行为,或者所有示例都是错误的?

【附加关键词:ctype cctype isalnum isalpha isblank iscntrl isdigit isgraph islowwer isprint ispunct isspace isupper isxdigit tolower]

Kev*_*hey 3

就其价值而言,Solaris Studio 编译器(使用stlport4)就是这样一种编译器套件,它会在这里产生意外的结果。编译并运行:

\n\n
#include <stdio.h>\n#include <cctype>\n\nint main() {\n    char ch = \'\\xa1\'; // \'\xc2\xa1\' in latin-1 locales + UTF-8\n    printf("is whitespace: %i\\n", std::isspace(ch));\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

给我:

\n\n
kevin@solaris:~/scratch\n$ CC -library=stlport4 whitespace.cpp && ./a.out \nis whitespace: 8\n
Run Code Online (Sandbox Code Playgroud)\n\n

以供参考:

\n\n
$ CC -V\nCC: Studio 12.5 Sun C++ 5.14 SunOS_i386 2016/05/31\n
Run Code Online (Sandbox Code Playgroud)\n\n

当然,这种行为已记录在 C++ 标准中,但这绝对令人惊讶。

\n\n
\n\n

编辑:由于有人指出上述版本在尝试分配时char ch = \'\\xa1\'由于整数溢出而包含未定义的行为,因此这里的版本可以避免这种情况并仍然保留相同的输出:

\n\n
#include <stdio.h>\n#include <cctype>\n\nint main() {\n    char ch = -95;\n    printf("is whitespace: %i\\n", std::isspace(ch));\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

在我的 Solaris VM 上仍然打印 8:

\n\n
kevin@solaris:~/scratch\n$ CC -library=stlport4 whitespace.cpp && ./a.out \nis whitespace: 8\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

编辑2:这里的程序可能看起来很正常,但由于 UB 在使用中给出了意想不到的结果std::isspace()

\n\n
#include <cstdio>\n#include <cstring>\n#include <cctype>\n\nstatic int count_whitespace(const char* str, int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        if (std::isspace(str[i]))  // oops!\n            count += 1;\n    return count;\n}\n\nint main() {\n    const char* batman = "I am batman\\xa1";\n    int n = std::strlen(batman);\n    std::printf("%i\\n", count_whitespace(batman, n));\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

并且,在我的 Solaris 计算机上:

\n\n
kevin@solaris:~/scratch\n$ CC whitespace.cpp && ./a.out\n3\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,根据您如何排列此程序,您可能会得到两个空白字符的预期结果;也就是说,几乎可以肯定存在一些编译器优化,利用此 UB 更快地给出错误结果。

\n\n

例如,如果您尝试通过搜索字符串中的(非多字节)空白字符来标记 UTF-8 字符串,您可以想象这会令您感到痛苦。这样的程序在转换str[i]unsigned char.

\n