¿Sería romper el lenguaje o código existente si añadiríamos seguro firmado / sin signo se compara con C / C ++?

StackOverflow https://stackoverflow.com/questions/3476590

Pregunta

Después de leer esta pregunta sobre firmado / sin signo compara (que surgen cada par de días yo diría):

Me preguntaba por qué no nos hemos adecuado firmado sin firmar y en su lugar se compara este horrible lío? Tome la salida de este pequeño programa:

#include <stdio.h>
#define C(T1,T2)\
 {signed   T1 a=-1;\
 unsigned T2 b=1;\
  printf("(signed %5s)%d < (unsigned %5s)%d = %d\n",#T1,(int)a,#T2,(int)b,(a<b));}\

 #define C1(T) printf("%s:%d\n",#T,(int)sizeof(T)); C(T,char);C(T,short);C(T,int);C(T,long);
int main()
{
 C1(char); C1(short); C1(int); C1(long); 
}

Compilado con mi compilador estándar (gcc, 64 bits), me sale esto:

char:1
(signed  char)-1 < (unsigned  char)1 = 1
(signed  char)-1 < (unsigned short)1 = 1
(signed  char)-1 < (unsigned   int)1 = 0
(signed  char)-1 < (unsigned  long)1 = 0
short:2
(signed short)-1 < (unsigned  char)1 = 1
(signed short)-1 < (unsigned short)1 = 1
(signed short)-1 < (unsigned   int)1 = 0
(signed short)-1 < (unsigned  long)1 = 0
int:4
(signed   int)-1 < (unsigned  char)1 = 1
(signed   int)-1 < (unsigned short)1 = 1
(signed   int)-1 < (unsigned   int)1 = 0
(signed   int)-1 < (unsigned  long)1 = 0
long:8
(signed  long)-1 < (unsigned  char)1 = 1
(signed  long)-1 < (unsigned short)1 = 1
(signed  long)-1 < (unsigned   int)1 = 1
(signed  long)-1 < (unsigned  long)1 = 0

Si Compilo de 32 bits, el resultado es el mismo, excepto que:

long:4
(signed  long)-1 < (unsigned   int)1 = 0

El "¿Cómo?" de todo esto es fácil de encontrar: Sólo Goto sección 6.3 de la norma C99 o el capítulo 4 de C ++ y desenterrar las cláusulas que describen cómo los operandos se convierten en un tipo común y esto puede romperse si los reinterpreta tipo común valores negativos <. / p>

Pero qué pasa con el "¿Por qué?". Como podemos ver, el '<' falla en el 50% de todos los casos, también depende de los tamaños concretos de los tipos, por lo que es dependiente de la plataforma. He aquí algunos puntos a considerar:

  • El proceso de conversión y comparar no es realmente un buen ejemplo para el estado de mínima sorpresa

  • No creo que no hay código por ahí, que se basa en la proposición de que (short)-1 > (unsigned)1 y es no escrito por terroristas.

  • Todo esto es terrible cuando estás en C ++ con código de la plantilla, ya que es necesario escribir rasgo de magia para tejer una correcta "<".


Después de todo, la comparación firmado y valor sin signo de diferentes tipos es fácil de implementar:

signed X < unsigned Y -> (a<(X)0) || ((Z)a<(Z)b) where Z=X|Y 

El pre-registro es barato y también puede ser optimizado de distancia por el compilador si a> = 0 se puede probar de forma estática.

Así que aquí está mi pregunta:

¿Sería romper el lenguaje o código existente si añadiríamos seguro firmado / sin signo se compara con C / C ++?

( "¿Sería romper el lenguaje" medios tendrían que tenga que hacer cambios masivos en diferentes partes de la lengua para dar cabida a este cambio)


ACTUALIZACIÓN: Tengo funcioné esto en mi viejo Turbo-C ++ 3.0 y tengo esta salida:

char:1
(signed  char)-1 < (unsigned  char)1 = 0

¿Por qué es (signed char)-1 < (unsigned char) == 0 aquí?

¿Fue útil?

Solución

Si se rompería el código de idioma / existente. El lenguaje, como usted ha señalado, especifica claramente el comportamiento cuando firmados y no firmados operandos se utilizan juntos. Este comportamiento con operadores de comparación es esencial para algunos idiomas importantes, como:

if (x-'0' < 10U)

Por no hablar de cosas como (comparación de igualdad):

size_t l = mbrtowc(&wc, s, n, &state);
if (l==-1) ... /* Note that mbrtowc returns (size_t)-1 on failure */

Como acotación al margen, especificar el comportamiento "natural" para las comparaciones mixtos firmados / no firmados que también incurrir en una penalización de rendimiento significativo, incluso en programas que actualmente están utilizando este tipo de comparaciones de manera segura en los que ya tienen su comportamiento "natural" debido a las limitaciones en la entrada que el compilador tendría dificultades para determinar (o podría no ser capaz de determinar en absoluto). Al escribir su propio código para manejar estas pruebas, estoy seguro de que ya ha visto lo que la penalización de rendimiento se vería así, y no es bonito.

Otros consejos

Mi respuesta es para C solamente.

No hay ningún tipo en C que puede acomodar todos los valores posibles de todos los posibles tipos de enteros. El más cercano C99 trata de este es intmax_t y uintmax_t, y su intersección solamente cubre la mitad de su respectivo rango.

Por lo tanto, no se puede aplicar una comparación de valor matemático como x <= y convirtiendo primero x y y a un tipo común y luego hacer una simple operación. Este es un cambio importante de un principio general de cómo funcionan los operadores. También rompe la intuición de que los operadores se corresponden con las cosas que tienden a ser las instrucciones individuales de hardware común.

Incluso si se ha añadido esta complejidad adicional para el lenguaje (y la carga extra para los escritores de implementación), sería no tener propiedades muy agradables. Por ejemplo, x <= y todavía no sería equivalente a x - y <= 0. Si querías todas estas propiedades agradables, que tendría que hacer números enteros de tamaño arbitrario parte del lenguaje.

Estoy seguro de que hay un montón de código UNIX de edad por ahí, posiblemente, algunos corriendo en su máquina, que asume que (int)-1 > (unsigned)1. (Ok, tal vez fue escrito por combatientes de la libertad; -)

Si desea Lisp / Haskell / Python / $ favorite_language_with_bignums_built_in, ya sabes dónde encontrarlo ...

No creo que rompería el idioma, pero sí, podría romper algún código existente (y la rotura sería probablemente difícil de detectar a nivel del compilador).

No existe mucho más código escrito en C y C ++ que tú y yo juntos puede imaginar (algunas de ellas pueden ser incluso escritos por los terroristas).

Confiar en la "proposición de que (short)-1 > (unsigned)1" se puede hacer por alguien sin querer. Existe una gran cantidad de tráfico de C código con la manipulación de bits complejo y cosas similares. Es muy posible algún programador puede ser el uso de la comparación del comportamiento actual en dicho código. (Otras personas ya han proporcionado buenos ejemplos de este tipo de código y un código es aún más simple que yo esperaría).

Solución actual es advertir en este tipo de comparaciones en su lugar, y dejar la solución para el programador, que creo que es un espíritu cómo funciona C y C ++. Además, la solución en un nivel compilador incurrir en una penalización de rendimiento, y esto es algo que los programadores de C y C ++ son extremadamente sensibles al. Dos pruebas en lugar de uno puede parecer un problema menor para usted, pero hay probablemente un montón de código en C, donde esto sería un problema. Podría ser resuelto, por ejemplo, forzando el comportamiento anterior mediante el uso de conversiones explícitas a un tipo de datos común - pero esto requeriría de nuevo programador de atención, por lo tanto, no es mejor que una simple advertencia

.

Creo que C ++ es como el imperio romano. Es grande, y demasiado establecido para arreglar las cosas que van a destruirlo.

c ++ 0x - e impulso - son ejemplos de una sintaxis horrible, horrible, - el tipo de bebé sólo sus padres puede amar - y son un largo camino desde el elegante (pero severamente limitada) en C ++ simple de hace 10 años.

El punto es, por el momento nadie ha "fijado" algo tan terriblemente simple como comparaciones de los tipos enteros, lo suficientemente legado y el código existente C ++ se ha roto que uno puede que también acaba de llamar a un nuevo idioma.

Y una vez roto, hay tantas otras cosas que también es elegible para la fijación retroactiva.

Las únicas maneras para que un lenguaje para definir reglas que puede acercarse a la defensa del principio de la menor sorpresa en tiempo de ejecución cuando se utiliza la combinación de operandos de diferentes tipos de lenguaje C sería o bien tienen el compilador no lo permita conversiones de tipo implícitas en por lo menos algunos contextos cambiantes (la 'sorpresa' a "¿por qué no esta compilación?" y lo que es menos probable que cause errores inesperados en el camino), definen varios tipos para cada formato de almacenamiento (por ejemplo, ambos envoltura y no envolver variantes de cada tipo de número entero), o ambos.

Tener varios tipos para cada formato de almacenamiento, por ejemplo, tanto envolver y versiones de enteros de 16 bits y sin signo de no envoltura, podrían permitir que el compilador de distinguir entre "Estoy usando un valor de 16 bits aquí en caso de que hace las cosas más eficiente, pero nunca va a exceder el rango 0-65535 y no me importa lo que pasó si lo hiciera )" y 'estoy usando un valor de 16 bits que necesita para envolver a 65535 se hace negativa'. En este último caso, un compilador que se utiliza un registro de 32 bits para el valor tal tendría para enmascarar después de cada operación aritmética, pero en el primer caso, el compilador podría omitir eso. Con respecto a su deseo particular, el significado de una comparación entre un no-envoltura firmó largo y un no envolver unsigned long sería clara, y sería apropiado para un compilador para generar la multi- secuencia de instrucciones necesarias para que esto ocurra (ya que la conversión de un número negativo a un unsigned long no envoltura sería un comportamiento indefinido, teniendo el compilador definir un comportamiento de los operadores de comparación de ese tipo sería no conflicto con cualquier otra cosa que se podría especificar).

Por desgracia, más allá de tener el compilador generará advertencias de mixtos de operando comparaciones, yo realmente no veo mucho de lo que se puede hacer con el lenguaje C, tal como existe sin añadir nuevos tipos a como se ha descrito anteriormente; aunque me gustaría considerar la adición de estos nuevos tipos como una mejora, no me contengo la respiración.

Si una comparación entre los tipos enteros comparó los valores matemáticos reales, me gustaría que ocurra lo mismo para las comparaciones entre números enteros y de coma flotante. Y la comparación de los valores exactos de un entero arbitrario de 64 bits y un número de coma flotante de doble precisión arbitraria es bastante difícil. Pero entonces el compilador probablemente sería mejor que a mí.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top