在调用 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::toupperstd::tolower(以及类似函数)之前,有必要将 char-argument 转换为 unsigned char

另一方面,Bjarne Stroustrup 在C++ 编程语言中没有提到这样做的必要性。他只是使用toupperlike

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,因为charunsigned char 具有相同的大小。

那么这个演员阵容是不必要的还是 Stroustrup 粗心的?

编辑:libstdc++ manual 提到输入字符必须来自basic source character set,但不强制转换。我想@Keith Thompson 的回复已经涵盖了这一点,他们都有一个积极的表示为signed charunsigned char

【问题讨论】:

如果可能的话,如果您能发布评论的链接,那就太好了。 You may find this an interesting read,[c] toupper cast 的热门歌曲之一。 @dyp ***.com/a/20182481/3002139 【参考方案1】:

是的,toupper 的参数需要转换为 unsigned char 以避免未定义行为的风险。

charsigned charunsigned char 类型是三种不同的类型。 chareither 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_MINUCHAR_MAX 的所有值),但不是必须这样做。此外,<ctype.h> 中的函数需要接受值为EOF 的参数,通常为-1

C++ 标准对一些 C 标准库函数进行了调整。例如,strchr 和其他几个函数被强制const 正确性的重载版本替换。 <cctype>中声明的函数没有这样的调整。

【讨论】:

我给了你一个+1,因为答案很好。但是你为什么在 C++ 问题中引用 C 标准呢? @JonathanMee:问得好。这是因为 C++ 继承了 C 的大部分标准库,并将其定义推迟到 C 标准。 intchar 的转换是实现定义的,不是吗? @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。从intunsigned char 的任何转换都发生在函数内部。 没有人说unsigned char 不能表示大于 255 的值。 @dyp "从 intunsigned char 的任何转换都发生在函数内部。" --> 不太可能将EOF 转换为255。处理EOF 之后,转换为unsigned char 是合理的,但未指定该行为。【参考方案3】:

在 C 中,toupper(和许多其他函数)采用 ints,即使您希望它们采用 chars。此外,char 在某些平台上已签名,而在其他平台上未签名。

在调用toupper 之前转换为unsigned char 的建议对于C 是正确的。我认为C++ 中不需要它,只要你传递一个在范围内的int我找不到任何特定于 C++ 中是否需要它的信息。

如果您想回避这个问题,请使用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 库,但 &lt;ctype.h&gt; 从 C90 到 C99 再到 C11 并没有太大变化,如果有的话) .在少数情况下,C++ 对 C 标准库进行了更改,但我没有看到对 &lt;ctype.h&gt; 的任何此类更改。我认为 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)

C函数tolower,与toupper

C#慎用ToLower和ToUpper,小心把你的系统给拖垮了

为啥 putchar、toupper、tolow 等采用 int 而不是 char?