¿Necesito lanzar a char sin firmar antes de llamar a toupper?

Hace un tiempo, alguien con una gran reputación aquí en StackOverflow escribió en un comentario que es necesario lanzar un argumento de char a unsigned char antes de llamar a std::toupper (y funciones similares).

Por otro lado, Bjarne Stroustrup no menciona la necesidad de hacerlo en C ++ – Lenguaje de progtwigción. Él solo usa toupper como

 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 ser representable como unsigned char . Para mí, esto suena como válido para cada char ya que char y unsigned char tienen el mismo tamaño.

Entonces, ¿este lanzamiento 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 se emite. Supongo que esto está cubierto por la respuesta de @Keith Thompson, todos tienen una representación positiva como signed char y unsigned char .

Sí, el argumento para toupper debe convertirse en unsigned char para evitar el riesgo de un comportamiento indefinido.

Los tipos char , signed char y unsigned char son tres tipos distintos. char tiene el mismo rango y representación que el signed char o el unsigned char . (El char normal está muy comúnmente firmado y puede representar valores en el rango -128 .. + 127).

La función toupper toma un argumento int y devuelve un resultado int . Citando el estándar C, sección 7.4, párrafo 1:

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

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

El operador de indexación [] en std::string devuelve un valor de char . Si el char simple es un tipo firmado, y si el valor devuelto por el name[0] pasa a ser negativo, entonces la expresión

 toupper(name[0]) 

tiene un comportamiento indefinido

El lenguaje garantiza que, incluso si se firma un char simple, todos los miembros del conjunto de caracteres básicos tienen valores no negativos, por lo que, dada la inicialización

 string name = "Niels Stroustrup"; 

el progtwig no se arriesga a un comportamiento indefinido. Pero sí, en general, un valor de char pasado a toupper (o a cualquiera de las funciones declaradas en / necesita convertirse en unsigned char , de modo que la conversión implícita a int no arroje un resultado negativo valorar y causar un comportamiento indefinido.

Las funciones se implementan comúnmente utilizando 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 int es de 32 bits, convertir el valor de char -2 a unsigned produce 4294967294 . Esto se convierte implícitamente en int (el tipo de parámetro), que probablemente rinde -2 .

toupper puede implementarse para que se comporte de manera sensata con los valores negativos (aceptando todos los valores desde CHAR_MIN hasta UCHAR_MAX ), pero no es obligatorio. Además, las funciones en son necesarias para aceptar un argumento con el valor EOF , que generalmente es -1 .

El estándar C ++ realiza ajustes en algunas funciones de biblioteca estándar de C. Por ejemplo, strchr y varias otras funciones son reemplazadas por versiones sobrecargadas que imponen la corrección const . No hay tales ajustes para las funciones declaradas en .

En C, toupper (y muchas otras funciones) toman int s aunque esperas que tomen char . Además, char está firmado en algunas plataformas y sin firmar en otras.

El consejo de enviar a unsigned char antes de llamar a toupper es correcto para C. No creo que sea necesario en C ++, siempre que lo pases por un int que esté dentro del rango. No puedo encontrar nada específico sobre si es necesario en C ++.

Si desea eludir el problema, use el toupper definido en . Es una plantilla y toma cualquier tipo de carácter aceptable. También debe pasarlo por std::locale . Si no tiene idea de qué configuración regional elegir, use std::locale("") , que se supone que es la configuración regional preferida del usuario:

 #include  #include  #include  #include  #include  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; } 

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

Lamentablemente Stroustrup fue descuidado 🙁
Y sí, los códigos de letras latinas deberían ser no negativos (y no se requiere molde) …
Algunas implementaciones funcionan correctamente sin conversión a char sin firmar …
Por alguna experiencia, puede costar varias horas encontrar la causa de segfault de un toupeper de este tipo (cuando se sabe que hay una segfault) …
Y también hay isupper, islower, etc.

En lugar de lanzar el argumento como char sin signo, puede lanzar la función. Deberá incluir un encabezado funcional . Aquí hay un código de muestra:

 #include  #include  #include  #include  #include  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)::toupper); std::cout << "uppercase name: " << name << '\n'; return 0; } 

El resultado es:

 uppercase name: DANIEL BRüHL 

Como era de esperar, toupper no tiene ningún efecto sobre los personajes no ascii. Pero este casting es beneficioso para evitar comportamientos inesperados.