¿Necesito convertir a un carácter sin firmar antes de llamar a toupper(), tolower(), et al.?

Resuelto Baum mit Augen asked hace 10 años • 5 respuestas

Hace un tiempo, alguien con gran reputación aquí en Stack Overflow escribió en un comentario que es necesario emitir un charargumento unsigned charantes de llamar std::touppera y std::tolower(y funciones similares).

Por otro lado, Bjarne Stroustrup no menciona la necesidad de hacerlo en el Lenguaje de Programación C++ . Él solo usa touppercomo

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"
}

(Citado de dicho libro, 4ª edición.)

La referencia dice que la entrada debe poder representarse como unsigned char. Para mí, esto parece que vale para todos charlos tiempos chary unsigned chartiene el mismo tamaño.

Entonces, ¿este reparto es innecesario o Stroustrup fue descuidado?

Editar: El manual de libstdc++ menciona que el carácter de entrada debe ser del conjunto de caracteres fuente básico , pero no lo convierte. Supongo que esto está cubierto por la respuesta de @Keith Thompson, todos tienen una representación positiva como signed chary unsigned char?

Baum mit Augen avatar Feb 16 '14 07:02 Baum mit Augen
Aceptado

toupperSí, es necesario convertir el argumento a unsigned charpara evitar el riesgo de un comportamiento indefinido.

Los tipos char, signed chary unsigned charson tres tipos distintos. chartiene el mismo rango y representación que o . (El formato normal suele estar firmado y puede representar valores en el rango -128..+127).signed char unsigned charchar

La toupperfunción toma un intargumento y devuelve un intresultado. Citando la norma C, sección 7.4 párrafo 1:

En todos los casos el argumento es un int, cuyo valor será representable como un unsigned charo será igual al valor de la macro EOF. Si el argumento tiene cualquier otro valor, el comportamiento no está definido.

(C++ incorpora la mayor parte de la biblioteca estándar de C y difiere su definición al estándar C).

El []operador de indexación on std::stringdevuelve una referencia a char. Si simple chares un tipo con signo y si el valor de name[0]resulta ser negativo, entonces la expresión

toupper(name[0])

tiene un comportamiento indefinido.

El lenguaje garantiza que, incluso si el formato simple charestá firmado, todos los miembros del conjunto de caracteres básico tienen valores no negativos, por lo que dada la inicialización

string name = "Niels Stroustrup";

el programa no corre el riesgo de tener un comportamiento indefinido. Pero sí, en general, un charvalor pasado a toupper(o a cualquiera de las funciones declaradas en <cctype>/ <ctype.h>) debe convertirse a unsigned char, de modo que la conversión implícita a intno produzca un valor negativo y provoque un comportamiento indefinido.

Las <ctype.h>funciones se implementan comúnmente mediante una tabla de búsqueda. Algo como:

// assume plain char is signed
char c = -2;
c = toupper(c); // undefined behavior

puede indexar fuera de los límites de esa tabla.

Tenga en cuenta que la conversión a unsigned:

char c = -2;
c = toupper((unsigned)c); // undefined behavior

no evita el problema. Si intes de 32 bits, al convertir el charvalor -2a unsignedse obtiene 4294967294. Luego, esto se convierte implícitamente a int(el tipo de parámetro), lo que probablemente produce -2.

toupper se puede implementar para que se comporte de manera sensata con valores negativos (aceptando todos los valores desde CHAR_MINhasta UCHAR_MAX), pero no es necesario hacerlo. Además, las funciones <ctype.h>deben aceptar un argumento con el valor EOF, que normalmente es -1.

El estándar C++ realiza ajustes en algunas funciones de la biblioteca estándar de C. Por ejemplo, strchrvarias funciones más se reemplazan por versiones sobrecargadas que imponen constla corrección. No existen tales ajustes para las funciones declaradas en <cctype>.

Keith Thompson avatar Feb 16 '2014 01:02 Keith Thompson

La referencia se refiere a que el valor se puede representar como unsigned char, no a que seaunsigned char . Es decir, el comportamiento no está definido si el valor real no está entre 0 y UCHAR_MAX (normalmente 255). (O EOF, que es básicamente la razón por la que se necesita un inten lugar de un char).

Sneftel avatar Feb 16 '2014 00:02 Sneftel

En C, toupper(y muchas otras funciones) toman ints aunque es de esperar que tomen chars. Además, charestá firmado en algunas plataformas y sin firmar en otras.

El consejo de transmitir unsigned charantes de llamar toupperes correcto para C. No creo que sea necesario en C++, siempre que lo pase y intesté dentro del rango. No puedo encontrar nada específico sobre si es necesario en C++.

Si desea evitar el problema, utilice lo toupperdefinido en<locale> . Es una plantilla y acepta cualquier tipo de carácter aceptable. También tienes que pasarle un std::locale. Si no tiene idea de qué configuración regional elegir, utilice std::locale(""), que se supone que es la configuración regional preferida del usuario:

#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;
}
Max Lybbert avatar Feb 16 '2014 00:02 Max Lybbert

Lamentablemente, Stroustrup fue descuidado :-(
Y sí, los códigos de letras latinas no deben ser negativos (y no se requiere conversión)...
Algunas implementaciones funcionan correctamente sin convertir caracteres sin firmar...
Según cierta experiencia, puede costar varios horas para encontrar la causa de la falla de segmento de dicho toupper (cuando se sabe que hay una falla de segmento)...
Y también hay isupper, islower, etc.

user3277268 avatar Feb 16 '2014 03:02 user3277268