sirve que para long data c++ unsigned-integer size-t

c++ - que - ¿Por qué size_t no está firmado?



short c++ para que sirve (4)

Bjarne Stroustrup escribió en The C ++ Programming Language:

Los tipos enteros sin signo son ideales para usos que tratan el almacenamiento como una matriz de bits. Usar unsigned en lugar de int para ganar un bit más para representar enteros positivos casi nunca es una buena idea. Los intentos de garantizar que algunos valores sean positivos declarando variables sin firmar serán derrotados por las reglas de conversión implícitas.

size_t parece estar sin signo "para ganar un bit más para representar enteros positivos". Entonces, ¿fue esto un error (o una desventaja), y si es así, deberíamos minimizar su uso en nuestro propio código?

Otro artículo relevante de Scott Meyers está here . Para resumir, recomienda no utilizar interfaces sin signo, independientemente de si el valor siempre es positivo o no. En otras palabras, incluso si los valores negativos no tienen sentido, no necesariamente debe usar unsigned.


Por otra parte ...

Mito 1 : std::size_t no está firmado debido a restricciones heredadas que ya no se aplican.

Hay dos razones "históricas" comúnmente referidas aquí:

  1. sizeof devuelve std::size_t , que no ha sido firmado desde los días de C.
  2. Los procesadores tenían tamaños de palabra más pequeños, por lo que era importante exprimir ese rango extra de alcance.

Pero ninguna de estas razones, a pesar de ser muy antiguas, en realidad están relegadas a la historia.

sizeof aún devuelve un std::size_t que aún no está firmado. Si quiere interoperar con sizeof o con los contenedores de biblioteca estándar, tendrá que usar std::size_t .

Las alternativas son todas peores: podría deshabilitar las advertencias de comparación de signo / sin signo y las advertencias de conversión de tamaño y esperar que los valores siempre estén en los rangos de solapamiento para que pueda ignorar los errores latentes utilizando diferentes tipos de pares potencialmente introducidos. O podría hacer muchas comprobaciones de rango y conversiones explícitas. O bien, podría introducir su propio tipo de tamaño con inteligentes conversiones integradas para centralizar la verificación de rango, pero ninguna otra biblioteca usará su tipo de tamaño.

Y aunque la mayoría de la informática tradicional se realiza en procesadores de 32 y 64 bits, C ++ todavía se utiliza en microprocesadores de 16 bits en sistemas integrados, incluso hoy en día. En esos microprocesadores, a menudo es muy útil tener un valor del tamaño de una palabra que pueda representar cualquier valor en su espacio de memoria.

Nuestro nuevo código aún tiene que interoperar con la biblioteca estándar. Si nuestro nuevo código usó tipos firmados mientras que la biblioteca estándar continúa usando los que no están firmados, lo hacemos más difícil para cada consumidor que tiene que usar ambos.

Mito 2 : No necesitas ese pedazo extra. (Es decir, nunca va a tener una cadena de más de 2 GB cuando su espacio de direcciones es de solo 4 GB).

Los tamaños e índices no son solo para la memoria. Su espacio de direcciones puede ser limitado, pero puede procesar archivos que son mucho más grandes que su espacio de direcciones. Y si bien es posible que no tenga una cadena con más de 2 GB, puede tener cómodamente un conjunto de bits con más de 2Gbits. Y no se olvide de los contenedores virtuales diseñados para datos dispersos.

Mito 3 : siempre puedes usar un tipo con signo más ancho.

No siempre. Es cierto que para una variable local o dos, podría usar un std::int64_t (suponiendo que su sistema tenga uno) o un código signed long long y probablemente escriba un código perfectamente razonable. (Pero aún necesitará algunos lanzamientos explícitos y el doble de comprobación de límites o tendrá que desactivar algunas advertencias del compilador que podrían haberlo alertado sobre errores en otras partes de su código).

Pero, ¿y si estás construyendo una gran tabla de índices? ¿De verdad quieres dos o cuatro bytes adicionales para cada índice cuando solo necesitas un bit ? Incluso si tiene mucha memoria y un procesador moderno, hacer que la tabla sea dos veces más grande podría tener efectos perjudiciales en la localidad de referencia, y todas las comprobaciones de rango ahora son de dos pasos, lo que reduce la eficacia de la predicción de bifurcación. ¿Y qué pasa si no tienes todo ese recuerdo?

Mito 4 : la aritmética sin firma es sorprendente y antinatural.

Esto implica que la aritmética firmada no es sorprendente o de alguna manera más natural. Y, tal vez es cuando se piensa en términos de matemáticas donde todas las operaciones aritméticas básicas se cierran sobre el conjunto de todos los números enteros.

Pero nuestras computadoras no funcionan con enteros. Ellos trabajan con una fracción infinitesimal de los enteros. Nuestra aritmética firmada no se cierra sobre el conjunto de todos los enteros. Tenemos desbordamiento y subdesbordamiento. Para muchos, eso es tan sorprendente y antinatural, la mayoría simplemente lo ignoran.

Este es un error:

auto mid = (min + max) / 2; // BUGGY

Si se firman min y max , la suma podría desbordarse, y eso produce un comportamiento indefinido. La mayoría de nosotros rutinariamente nos percatamos de este tipo de errores porque olvidamos que la adición no se cierra sobre el conjunto de entradas firmadas. Nos salimos con la nuestra porque nuestros compiladores generalmente generan código que hace algo razonable (pero aún sorprendente).

Si min y max no están firmados, la suma aún podría desbordarse, pero el comportamiento indefinido desaparecerá. Seguirás recibiendo la respuesta incorrecta, por lo que sigue siendo sorprendente, pero no más sorprendente de lo que era con los formularios firmados.

La verdadera sorpresa sin signo viene con sustracción: si restas una int más grande sin firmar de una más pequeña, vas a terminar con un gran número. Este resultado no es más sorprendente que si dividieras por 0.

Incluso si pudieras eliminar tipos sin firmar de todas tus API, aún debes estar preparado para estas "sorpresas" sin firmar si trabajas con los contenedores estándar o formatos de archivos o protocolos cableados. ¿Realmente vale la pena agregar fricción a sus API para "resolver" solo parte del problema?


Una razón para hacer que los tipos de índice no estén firmados es para la simetría, con la preferencia de C y C ++ para los intervalos semiabiertos. Y si sus tipos de índice van a estar sin firmar, entonces es conveniente tener también su tipo de tamaño sin firmar.

En C, puede tener un puntero que apunta a una matriz. Un puntero válido puede apuntar a cualquier elemento de la matriz o un elemento más allá del final de la matriz. No puede señalar a un elemento antes del comienzo de la matriz.

int a[2] = { 0, 1 }; int * p = a; // OK ++p; // OK, points to the second element ++p; // Still OK, but you cannot dereference this one. ++p; // Nope, now you''ve gone too far. p = a; --p; // oops! not allowed

C ++ está de acuerdo y extiende esta idea a los iteradores.

Los argumentos en contra de los tipos de índice sin signo a menudo dan un ejemplo de atravesar una matriz de atrás hacia adelante, y el código a menudo se ve así:

// WARNING: Possibly dangerous code. int a[size] = ...; for (index_type i = size - 1; i >= 0; --i) { ... }

Este código solo funciona si index_type está firmado, lo que se usa como argumento de que los tipos de índice deben estar firmados (y que, por extensión, los tamaños deben estar firmados).

Ese argumento no es convincente porque ese código no es idiomático. Mire lo que sucede si tratamos de reescribir este ciclo con punteros en lugar de índices:

// WARNING: Bad code. int a[size] = ...; for (int * p = a + size - 1; p >= a; --p) { ... }

¡Sí, ahora tenemos un comportamiento indefinido! Ignorando el problema cuando el size es 0, tenemos un problema al final de la iteración porque generamos un puntero no válido que apunta al elemento antes que el primero. Es un comportamiento indefinido incluso si nunca intentamos desreferenciar ese puntero.

Por lo tanto, podría argumentar para solucionar esto cambiando el estándar de idioma para que sea legítimo tener un puntero que apunte al elemento antes que el primero, pero eso no es probable que suceda. El intervalo medio abierto es un componente básico de estos lenguajes, así que vamos a escribir un código mejor.

Una solución correcta basada en punteros es:

int a[size] = ...; for (int * p = a + size; p != a; ) { --p; ... }

Muchos encuentran esto perturbador porque la disminución está ahora en el cuerpo del bucle en lugar de en el encabezado, pero eso es lo que sucede cuando la sintaxis forzada está diseñada principalmente para bucles hacia adelante a través de intervalos medio abiertos. (Los iteradores inversos resuelven esta asimetría posponiendo la disminución).

Ahora, por analogía, la solución basada en índice se convierte en:

int a[size] = ...; for (index_type i = size; i != 0; ) { --i; ... }

Esto funciona independientemente de si index_type está firmado o no, pero la opción unsigned produce un código que se correlaciona más directamente con el puntero idiomático y las versiones de iterador. Unsigned también significa que, como con los punteros y los iteradores, podremos acceder a todos los elementos de la secuencia; no entregamos la mitad de nuestro rango posible para representar valores sin sentido. Si bien no es una preocupación práctica en un mundo de 64 bits, puede ser una preocupación muy real en un procesador integrado de 16 bits o en la construcción de un tipo de contenedor abstracto para datos escasos en un rango masivo que aún puede proporcionar la API idéntica como contenedor nativo.


size_t unsigned está unsigned porque los tamaños negativos no tienen sentido.

(De los comentarios :)

No se trata tanto de garantizar, como de decir lo que es. ¿Cuándo fue la última vez que vio una lista de tamaño -1? Siga esa lógica demasiado lejos y encontrará que unsigned no debería existir y las operaciones de bits tampoco deberían permitirse. - geekosaur

Más al punto: las direcciones, por razones que debe considerar, no están firmadas. Los tamaños se generan al comparar direcciones; tratar una dirección como firmada hará mucho lo incorrecto, y usar un valor firmado para el resultado perderá datos de una manera que su lectura de la cita de Stroustrup evidentemente cree que es aceptable, pero en realidad no lo es. Quizás puedas explicar qué debería hacer una dirección negativa en su lugar. - geekosaur


size_t no está firmado por razones históricas.

En una arquitectura con punteros de 16 bits, como la programación "pequeña" del modelo DOS, sería poco práctico limitar las cadenas a 32 KB.

Por esta razón, el estándar C requiere (a través de los rangos requeridos) ptrdiff_t , la contraparte firmada para size_t y el tipo de resultado de la diferencia del puntero, para ser efectivamente de 17 bits.

Esas razones aún se pueden aplicar en partes del mundo de la programación integrada.

Sin embargo, no se aplican a la programación moderna de 32 bits o 64 bits, donde una consideración mucho más importante es que las infortunadas reglas de conversión implícitas de C y C ++ convierten los tipos sin signo en atractores de errores, cuando se usan para números (y por lo tanto, operaciones aritméticas y comparaciones de magnitud). Con 20-20 retrospectiva, ahora podemos ver que la decisión de adoptar esas reglas particulares de conversión, donde por ejemplo string( "Hi" ).length() < -3 está prácticamente garantizado, era bastante tonta y poco práctica. Sin embargo, esa decisión significa que en la programación moderna, la adopción de tipos sin signo para números tiene desventajas graves y ninguna ventaja, salvo la satisfacción de los sentimientos de aquellos que unsigned tienen unsigned para ser un nombre de tipo autodescriptivo, y no piensan en typedef int MyType .

En resumen, no fue un error. Fue una decisión por razones de programación muy racionales y prácticas. No tiene nada que ver con la transferencia de las expectativas de los lenguajes limitados a los controles, como Pascal a C ++ (lo que es una falacia, pero muy común, incluso si algunos de los que lo hacen nunca han oído hablar de Pascal).