En c++ aparece la advertencia “warning: comparison between signed and unsigned integer expressions [-Wsign-compare]”

publicado por: Anonymous

metodo = find, npos

Aprendiendo sobre los métodos de string y usando find, me apareció una advertencia ejecutando el código en un compilador web. Pero al usar el mismo código en otro compilador (code::blocks 16.01) no me aparece tal advertencia.

Busqué información en internet, pero como la mayoría está en inglés, no he comprendido nada, y peor aún cuando lo traduje al español.

Quería preguntarles de qué se trata la advertencia:

10:14: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]

El código fuente:

Lo que hace el código es buscar si en la palabra que ingresaste hay un de y te dice donde está

#include <iostream>
#include <string>
using namespace std;

int main() {
   string s;
   cin >> s;
   const int p = s.find("de");
   if (p != s.npos) {//aqui aparece la advertencia
      cout << p << endl;
   } else {
      cout << "No." << endl;
   }
}

El link del compilador + el código: http://cpp.sh/3pir

Nota: originalmente donde aparece la advertencia había un string::npos, el cual lo modifiqué a s.npos .

solución

Formato de números con y sin signo en binario.

Los ordenadores almacenan números en binario; como no puedes guardar el símbolo de negativo - binariamente, se usa (habitualmente) el bit de más peso para marcar el signo del número.

Pondré un ejemplo con 8 bits:

|sgno|             numero               |
|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|
|  1 |    |    |    |    |    |    |  1 | = 129 (sin signo) -127 (con signo)
|    |    |    |    |    |    |    |  1 | = 1   (sin signo)    1 (con signo)

En un número de 8bits, el 7º es el bit de más peso. Si ese bit está a valor 1 Y el tipo es con signo el valor corresponde a -127; si el tipo es sin signo su valor corresponde a 129 ¡una gran diferencia!.

La cantidad de números que puede contener un tipo de 8bits es 28 (256), cuando se usa el bit de signo la mitad de esos 256 números serán positivos y la otra mitad negativos, así que:

                                 |mínimo|máximo|
unsigned char (8 bits sin signo) |    0 |  255 |
  signed char (8 bits con signo) |  127 | -127 |

Estos rangos de números pueden cambiar según si el complemento binario es a uno o a dos.

¿Por qué es este formato importante?

Como has podido ver, según si el número tiene o no tiene signo su valor cambia mucho, así que no es aconsejable compararlos aunque sea posible.

Se muestra un warning en lugar de un error porque es posible y correcto comparar tipos con singo y tipos sin signo siempre y cuando se comparen en el rango positivo de números… pero si se comparan tipos con singo contra tipos sin signo cuando los con signo están en el rango negativo, entonces pueden darse resultados inesperados.

El compilador no puede preveer a priori todas las operaciones que puedan hacerse con los números que intervienen en la comparación; así que prevée que cabe la posibilidad de que se haga una comparación potencialmente peligrosa: de ahí la alarma, en lugar de error.

La decisión que toma el compilador al comparar tipos con y sin signo está redactada en el estándar C++ apartado §4.7.2 (la traducción es mía):

Si el tipo de destino es sin signo, el valor resultante es el menor entero sin signo congruente con el entero de origen (modulo 2n donde n es el número de bits usado para representar el tipo sin signo). [ Nota: en representaciones numéricas de complemento a dos, esta conversión es conceptual y no hay cambio en el patrón de bits (si no hay truncado) – fin de la nota ]

Mientras que en el apartado §5.9.10 (la traducción es mía):

Varios operadores binarios que esperan operadores de tipo aritmético o enumerado causan conversiones y devuelven tipos de una manera similar. El propósito es devolver un tipo común, que es también el tipo del resultado. Este patrón es llamado conversiones aritméticas usuales, que se define así:

  • 10.5 … las promociones integrales deben ser aplicadas a ambos operandos. Entonces las siguientes reglas serán aplicadas a los operandos promocionados:
    • 10.5.1 Si ambos operandos tienen el mismo tipo, no se requiere ningia conversión adicional.
    • 10.5.2 En caso contrario, si ambos operandos tienen tipos con signo o ambos tipos tienen tipos sin signo, el operando con el tipo con menor rango será convertido al tipo con mayor rango.
    • 10.5.3 En caso contrario, si el operando que tiene tipo entero sin signo tiene un rango mayor o igual al rango del tipo del otro operando, el operando con tipo entero con signo debe ser convertido al tipo del operando con tipo entero sin signo.
    • 10.5.4 En caso contrario, si el tipo del operando con tipo entero con signo puede representar todos los valores del tipo del operando entero sin signo, el poerando con tipo entero sin signo debe ser convertido al tipo con el operando con tipo entero con signo.
    • 10.5.5 En caso contrario, ambos operandos deben ser convertidos a tipo entero sin signo correspondiendo al tipo del operando entero sin signo.

Este galimatías (el estándar suele ser complicado de entender) significa que si comparas tipos con signo contra tipos sin signo (o viceversa) se harán conversiones de tipos. Cabe la posibilidad que estas conversiones no te convengan o den lugar a resultados inesperados y/o indeseados.

Vale, lo pillo… ¿qué debo hacer?

Intenta usar siempre los mismos tipos para realizar comparaciones, si no conoces el tipo de una expresión puedes consultarlo en el código fuente o usar las herramientas del lenguaje para auto-detectarlo (C++11 o superior).

En tu caso, la función std::string::find devuelve un size_type el cuál es un alias del size_type de su allocator el cuál suele ser un alias de size_t (es algo más complicado, pero ya hemos tenido suficientes complicaciones por ahora). Y lo comparas contra std::string::npos cuyo tipo es el size_type del que ya hemos hablado. Así que podrías cambiar tu código a:

const size_t p = s.find("de");
if (p != s.npos) {

Lo que posiblemente eliminaría el error, o usar el tipo size_type interno de std::string:

const string::size_type p = s.find("de");
if (p != s.npos) {

Lo cuál eliminaría el error con toda seguridad, o auto-detectar el tipo:

const auto p = s.find("de");
if (p != s.npos) {

O si sospechamos que std::string::npos es de un tipo diferente al retorno de std::string::find podríamos copiar el tipo con decltype:

const decltype(s.npos) p = s.find("de");
if (p != s.npos) {

No se vayan, aún hay más.

me apareció una advertencia ejecutando el código en un compilador web. Pero al usar el mismo código en otro compilador (code::blocks 16.01) no me aparece tal advertencia.

La máquina usada en la compilación web y la máquina en que tienes instalado code::blocks pueden tener arquitecturas diferentes y diferentes implementaciones de las librerías de C++.

Arquitecturas diferentes pueden tener diferentes tamaños de tipos enteros por defecto y diferentes librerías de C++ pueden tener definiciones diferentes de string::size_type y size_t, con lo que pueden estar realizandose conversiones diferentes o ninguna conversión, lo cuál puede acabar en no emitir ninguna alarma…

… o podría ser que tengas desactivadas las alarmas en tu configuración de code::blocks.

Nota: originalmente donde aparece la advertencia había un string::npos, el cual lo modifiqué a s.npos.

npos está definido dentro de la clase plantilla std::basic_string como:

static const size_type npos = -1;

Es decir: es un miembro estático de string, los miembros estáticos de una clase se pueden acceder sin instanciar la clase, a través del nombre de la misma, así que puedes escribir string::npos… pero como forma parte también de string puedes acceder a npos desde una instancia, así que también puedes escribir s.npos y ambos son válidos. Pero esto último no es aconsejado.

Respondido por: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *