Нужно ли приводить к unsigned char перед вызовом toupper(), tolower() и др.?

Некоторое время назад кто-то с высокой репутацией на Stackru написал в комментарии, что необходимо разыграть charАргумент к unsigned char перед звонком std::toupper (и аналогичные функции).

С другой стороны, Бьярн Страуструп не упоминает о необходимости делать это на языке программирования C++. Он просто использует 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-е издание.)

Ссылка говорит, что вход должен быть представлен как unsigned char, Для меня это звучит так, как будто char поскольку char а также unsigned char имеют одинаковый размер.

Так что этот актёрский состав ненужен или Страуструп небрежен?

Редактировать: руководство libstdC++ упоминает, что входной символ должен быть из базового исходного набора символов, но не приводится. Я думаю, что это покрыто ответом @Keith Thompson, все они имеют положительное представление как signed char а также unsigned char?

5 ответов

Решение

Да, аргумент toupper должен быть преобразован в unsigned char чтобы избежать риска неопределенного поведения.

Типы char, signed char, а также unsigned char три разных типа. char имеет тот же диапазон и представление, что и любой signed char или же unsigned char, (Plain char очень часто подписывается и может представлять значения в диапазоне -128..+127.)

toupper функция занимает int аргумент и возвращает int результат. Цитируя стандарт С, раздел 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 значение передано 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

не избегает проблемы. Если int составляет 32 бита, преобразуя char значение -2 в unsigned доходность 4294967294, Затем это неявно преобразуется в int (тип параметра), который, вероятно, дает -2,

toupper может быть реализован таким образом, что он ведет себя разумно для отрицательных значений (принимая все значения из CHAR_MIN в UCHAR_MAX), но это не обязательно. Кроме того, функции в <ctype.h> должны принять аргумент со значением EOF, который обычно -1,

Стандарт C++ вносит коррективы в некоторые функции стандартной библиотеки C. Например, strchr и некоторые другие функции заменены перегруженными версиями, которые обеспечивают const правильность. Для функций, объявленных в <cctype>,

Ссылка относится к значению, представляемому как unsigned charне для того, чтобы быть unsigned char, То есть поведение не определено, если фактическое значение не находится между 0 и UCHAR_MAX (обычно 255). (Или же EOFчто является в основном причиной, по которой int вместо char.)

В С, toupper (и многие другие функции) взять int даже если вы ожидаете, что они примут char s. Дополнительно, char подписан на некоторых платформах и не подписан на других.

Совет бросить на unsigned char перед звонком toupper правильно для C. Я не думаю, что это необходимо в C++, если вы передадите его int это в диапазоне Я не могу найти ничего конкретного о том, нужно ли это в C++.

Если вы хотите обойти проблему, используйте toupper определяется в <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;
}

К сожалению, Страуструп был неосторожен:-(
И да, латинские буквенные коды должны быть неотрицательными (и приведение не требуется)...
Некоторые реализации корректно работают без приведения к unsigned char...
По определенному опыту, это может стоить нескольких часов, чтобы найти причину ошибки такого таппера (когда известно, что есть ошибка)...
И есть также isupper, islower и т. Д.

Вместо того, чтобы приводить аргумент как беззнаковый символ, вы можете привести функцию. Вам нужно будет включить функциональный заголовок. Вот пример кода:

#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. Но этот кастинг полезен для избежания неожиданного поведения.

Другие вопросы по тегам