【问题标题】: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

【问题讨论】:

标签: c++ language-lawyer undefined-behavior toupper tolower


【解决方案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 问题。
猜你喜欢
  • 1970-01-01
  • 2019-07-24
  • 2012-09-22
  • 2014-09-03
  • 1970-01-01
  • 2011-10-19
  • 2013-09-23
  • 2017-01-13
相关资源
最近更新 更多