在调用 toupper()、tolower() 等之前,我是不是需要转换为 unsigned char?
Posted
技术标签:
【中文标题】在调用 toupper()、tolower() 等之前,我是不是需要转换为 unsigned char?【英文标题】:Do I need to cast to unsigned char before calling toupper(), tolower(), et al.?在调用 toupper()、tolower() 等之前,我是否需要转换为 unsigned char? 【发布时间】:2014-03-15 08:49:05 【问题描述】:不久前,在 Stack Overflow 上享有盛誉的人在评论中写道,在调用 std::toupper
和 std::tolower
(以及类似函数)之前,有必要将 char
-argument 转换为 unsigned char
。
另一方面,Bjarne Stroustrup 在C++ 编程语言中没有提到这样做的必要性。他只是使用toupper
like
string name = "Niels Stroustrup"; void m3() string s = name.substr(6,10); // s = "Stroustr up" name.replace(0,5,"nicholas"); // name becomes "nicholas Stroustrup" name[0] = toupper(name[0]); // name becomes "Nicholas Stroustrup"
(引自该书,第 4 版。)
The reference 表示输入需要可以表示为unsigned char
。
对我来说,这听起来似乎适用于每个char
,因为char
和unsigned char
具有相同的大小。
那么这个演员阵容是不必要的还是 Stroustrup 粗心的?
编辑:libstdc++ manual 提到输入字符必须来自basic source character set,但不强制转换。我想@Keith Thompson 的回复已经涵盖了这一点,他们都有一个积极的表示为signed char
和unsigned char
?
【问题讨论】:
如果可能的话,如果您能发布评论的链接,那就太好了。 You may find this an interesting read,[c] toupper cast
的热门歌曲之一。
@dyp ***.com/a/20182481/3002139
【参考方案1】:
是的,toupper
的参数需要转换为 unsigned char
以避免未定义行为的风险。
char
、signed char
和 unsigned char
类型是三种不同的类型。 char
与 either signed char
或 unsigned char
具有相同的范围和表示。 (普通的char
是非常常用的有符号并且能够表示-128..+127 范围内的值。)
toupper
函数接受 int
参数并返回 int
结果。引用 C 标准,第 7.4 节第 1 段:
在所有情况下,参数都是
int
,其值应为 可表示为unsigned char
或应等于 宏EOF
。如果参数有任何其他值,则 行为未定义。
(C++ 包含了大部分 C 标准库,并将其定义推迟到 C 标准。)
std::string
上的[]
索引运算符返回对char
的引用。如果plain char
是有符号类型,而name[0]
的值恰好是负数,那么表达式
toupper(name[0])
有未定义的行为。
语言保证,即使简单的char
被签名,基本字符集的所有成员都具有非负值,所以在初始化时
string name = "Niels Stroustrup";
该程序不会冒未定义行为的风险。但是是的,一般来说,传递给toupper
(或<cctype>
/<ctype.h>
中声明的任何函数)的char
值需要转换为unsigned char
,以便隐式转换为int
不会产生负值并导致未定义的行为。
<ctype.h>
函数通常使用查找表来实现。比如:
// assume plain char is signed
char c = -2;
c = toupper(c); // undefined behavior
可能会超出该表的范围。
注意转换成unsigned
:
char c = -2;
c = toupper((unsigned)c); // undefined behavior
不能避免问题。如果int
是32 位,则将char
值-2
转换为unsigned
会产生4294967294
。然后将其隐式转换为int
(参数类型),可能产生-2
。
toupper
可以被实现,因此它对负值的行为是明智的(接受从CHAR_MIN
到UCHAR_MAX
的所有值),但不是必须这样做。此外,<ctype.h>
中的函数需要接受值为EOF
的参数,通常为-1
。
C++ 标准对一些 C 标准库函数进行了调整。例如,strchr
和其他几个函数被强制const
正确性的重载版本替换。 <cctype>
中声明的函数没有这样的调整。
【讨论】:
我给了你一个+1,因为答案很好。但是你为什么在 C++ 问题中引用 C 标准呢? @JonathanMee:问得好。这是因为 C++ 继承了 C 的大部分标准库,并将其定义推迟到 C 标准。 从int
到char
的转换是实现定义的,不是吗?
@KeithThompson 我的意思是,假设char
已签名并且值为-42。然后将其转换为unsigned char
(213) 和int
(213)。现在(char) 213
实现的结果不是定义了吗?
@L.F.:是的,好点子! (顺便说一句,它是 214,而不是 213。)或者它可以引发实现定义的信号,尽管我认为没有任何实现会这样做。在实践中,它不太可能导致任何问题。【参考方案2】:
引用指的是值 representable 作为 unsigned char
,而不是 unsigned char
。也就是说,如果实际值不在 0 和 UCHAR_MAX
(通常为 255)之间,则行为未定义。 (或EOF
,这基本上是它使用int
而不是char
的原因。)
【讨论】:
由于toupper
的参数是int
,我认为负的char
值可能会导致UB。从int
到unsigned char
的任何转换都发生在函数内部。
没有人说unsigned char
不能表示大于 255 的值。
@dyp "从 int
到 unsigned char
的任何转换都发生在函数内部。" --> 不太可能将EOF
转换为255。在处理EOF
之后,转换为unsigned char
是合理的,但未指定该行为。【参考方案3】:
在 C 中,toupper
(和许多其他函数)采用 int
s,即使您希望它们采用 char
s。此外,char
在某些平台上已签名,而在其他平台上未签名。
在调用toupper
之前转换为unsigned char
的建议对于C 是正确的。我认为C++ 中不需要它,只要你传递一个在范围内的我找不到任何特定于 C++ 中是否需要它的信息。int
。
如果您想回避这个问题,请使用toupper
defined in <locale>
。它是一个模板,可以采用任何可接受的字符类型。您还必须将其传递给std::locale
。如果您不知道选择哪种语言环境,请使用std::locale("")
,这应该是用户的首选语言环境:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <locale>
#include <string>
int main()
std::string name("Bjarne Stroustrup");
std::string uppercase;
std::locale loc("");
std::transform(name.begin(), name.end(), std::back_inserter(uppercase),
[&loc](char c) return std::toupper(c, loc); );
std::cout << name << '\n' << uppercase << '\n';
return 0;
【讨论】:
是的,这对 C 来说是正确的。为什么你认为同样的事情不适用于 C++? 如果您首先将int
传递给它,那么它在 C 中也不需要。如果您在 either 中传递char
,则需要它。
@KeithThompson 我没有检查标准,但坦率地说,我认为 C++ 不需要强制转换的原因仅仅是因为我只见过在 C 项目中强制转换的建议。可能我只是没有阅读正确的文章,但我发现有趣的是,我从未见过 C++ 专家提到需要演员表,而我看到 C 专家提到过。
C++ 通过引用包含了大部分 C 标准库(C++11 指的是 C99 库,但 <ctype.h>
从 C90 到 C99 再到 C11 并没有太大变化,如果有的话) .在少数情况下,C++ 对 C 标准库进行了更改,但我没有看到对 <ctype.h>
的任何此类更改。我认为 C++ 专家只是缺少一些东西。 (toupper(c)
是“安全的”,如果它的参数已知在基本字符集中。)【参考方案4】:
可悲的是,Stroustrup 粗心了 :-( 是的,拉丁字母代码应该是非负数(并且不需要强制转换)... 某些实现无需转换为无符号字符即可正常工作... 根据一些经验,找到此类 toupper 的段错误的原因可能需要几个小时(当已知存在段错误时)... 还有isupper、islower等
【讨论】:
可以说,小心ful - 问题中的示例仅使用源字符集中的字符。无论char
是签名还是未签名,它们都可以保证工作。【参考方案5】:
您可以将函数转换为函数,而不是将参数转换为 unsigned char。您将需要包含 functional 标头。这是一个示例代码:
#include <string>
#include <algorithm>
#include <functional>
#include <locale>
#include <iostream>
int main()
typedef unsigned char BYTE; // just in case
std::string name("Daniel Brühl"); // used this name for its non-ascii character!
std::transform(name.begin(), name.end(), name.begin(),
(std::function<int(BYTE)>)::toupper);
std::cout << "uppercase name: " << name << '\n';
return 0;
输出是:
uppercase name: DANIEL BRüHL
正如预期的那样,toupper 对非 ascii 字符没有影响。但是这种强制转换有利于避免意外行为。
【讨论】:
这似乎可行,但似乎相当复杂。它比提出论点更好吗? @KeithThompson 请注意,函数toupper
是作为 lambda 表达式传递的。所以在这种情况下,最好是强制转换函数。
"正如预期的那样,toupper 对非 ascii 字符没有影响" --> 肯定是 locale 问题。以上是关于在调用 toupper()、tolower() 等之前,我是不是需要转换为 unsigned char?的主要内容,如果未能解决你的问题,请参考以下文章
C语言中 toupper()和tolower()用法?请大神详述 谢谢!!!
linq 不区分大小写(没有 toUpper 或 toLower)
ccf 201409-3 字符串匹配(toupper,tolower)