在调用toupper(),tolower()等之前,是否需要转换为unsigned char?

时间:2014-02-16 00:30:40

标签: c++ c casting undefined-behavior toupper

前段时间,StackOverflow上有名望的人在评论中写道,在调用char(和类似函数)之前,有必要将unsigned char - 参数强制转换为std::toupper

另一方面,Bjarne Stroustrup没有提到在C ++ - Programming Language中这样做的必要性。 他只使用toupper喜欢

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的正面陈述?

5 个答案:

答案 0 :(得分:27)

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

类型charsigned charunsigned char是三种不同的类型。 char具有与 signed char unsigned char相同的范围和表示形式。 (普通char通常是签名的,能够表示-128 .. + 127范围内的值。)

toupper函数采用int参数并返回int结果。引用C标准,第7.4节第1段:

  

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

(C ++包含大部分C标准库,并将其定义推迟到C标准。)

[]上的std::string索引运算符返回char值。如果普通char是签名类型,并且name[0]返回的值恰好为负数,那么表达式

toupper(name[0])

有未定义的行为。

该语言保证即使普通char已签名,基本字符集的所有成员都具有非负值,因此初始化

string name = "Niels Stroustrup";

该程序不会冒未定义行为的风险。但是,是的,通常传递给char的{​​{1}}值(或toupper / <cctype>中声明的任何函数都需要转换为<ctype.h>,因此隐式转换为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 为32位,则将intchar转换为-2会产生unsigned。然后将其隐式转换为4294967294(参数类型),可能产生int

-2 可以实施,因此它对负值表现得合理(接受toupperCHAR_MIN的所有值),但不需要这样做。此外,UCHAR_MAX中的函数需要接受值为<ctype.h>的参数,通常为EOF

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

答案 1 :(得分:3)

引用是指可表示的值为unsigned char,而不是 unsigned char。也就是说,如果实际值不在0和UCHAR_MAX之间(通常为255),则行为是不确定的。 (或EOF,这基本上是int代替char的原因。)

答案 2 :(得分:2)

在C中,toupper(以及许多其他功能)取int s,即使您希望它们采用char s。此外,char在某些平台上签名,在其他平台上签名。

在调用unsigned char之前强制转换为toupper的建议对于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;
}

答案 3 :(得分:1)

悲伤的Stroustrup粗心大意:-(
是的,拉丁字母代码应该是非负的(并且不需要演员)...
某些实现正确工作,无需转换为unsigned char ...
根据一些经验,可能需要花费几个小时来找到这种toupper的段错误(当知道有段错误时)...... 还有isupper,islower等

答案 4 :(得分:0)

您可以强制转换函数,而不是将参数转换为unsigned char。您需要包含功能标头。这是一个示例代码:

#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字符没有影响。但这种铸造有利于避免意外行为。