tipos sintaxis funciones ejemplos datos comandos codigos caracteristicas basicos c++

funciones - sintaxis de c++



¿Todavía hay una razón para usar `int` en el código C++? (10)

Pro

¿Más fácil de escribir, supongo? Pero siempre puedes typedef .

Muchas API utilizan int, incluidas partes de la biblioteca estándar. Históricamente, esto ha causado problemas, por ejemplo, durante la transición a archivos de 64 bits.

Debido a las reglas predeterminadas de promoción de tipos, los tipos más estrechos que int podrían ampliarse a int o unsigned int a menos que agregue conversiones explícitas en muchos lugares, y muchos tipos diferentes podrían ser más estrechos que int en alguna implementación en alguna parte. Entonces, si te importa la portabilidad, es un dolor de cabeza menor.

Estafa

También uso ptrdiff_t para índices, la mayoría de las veces. (Estoy de acuerdo con Google en que los índices sin firmar son un atractor de errores). Para otros tipos de matemáticas, hay int_fast64_t . int_fast32_t , etc., que también será tan bueno o mejor que int . Casi ningún sistema del mundo real, con la excepción de algunos Unices difuntos del siglo pasado, usa ILP64, pero hay muchas CPU en las que desearía matemática de 64 bits. Y un compilador está técnicamente permitido, de manera estándar, para romper su programa si int es mayor que 32,767.

Dicho esto, cualquier compilador de C que valga la pena se probará en una gran cantidad de código que agrega un int puntero dentro de un bucle interno. Entonces no puede hacer nada demasiado tonto. El peor de los casos en el hardware actual es que necesita una instrucción adicional para firmar-extender un valor firmado de 32 bits a 64 bits. Pero, si lo que realmente desea es la matemática de puntero más rápida, la matemática más rápida para valores con una magnitud entre 32 kibi y 2 gibi, o la memoria menos desperdiciada, debe decir lo que quiere decir, no hacer que el compilador adivine.

Muchas guías de estilo, como la de Google, recomiendan usar int como un entero predeterminado al indexar matrices, por ejemplo. Con el aumento de las plataformas de 64 bits, donde la mayoría de las veces un int es de solo 32 bits, que no es el ancho natural de la plataforma. Como consecuencia, no veo ninguna razón, aparte de la simple, para mantener esa elección. Vemos claramente que compilamos el siguiente código:

double get(const double* p, int k) { return p[k]; }

que se compila en

movslq %esi, %rsi vmovsd (%rdi,%rsi,8), %xmm0 ret

donde la primera instrucción promueve el entero de 32 bits en un entero de 64 bits.

Si el código se transforma en

double get(const double* p, std::ptrdiff_t k) { return p[k]; }

el ensamblado generado es ahora

vmovsd (%rdi,%rsi,8), %xmm0 ret

lo que muestra claramente que la CPU se siente más en casa con std::ptrdiff_t que con un int . Muchos usuarios de C ++ se han mudado a std::size_t , pero no quiero usar enteros sin signo a menos que realmente necesite un comportamiento de módulo 2^n .

En la mayoría de los casos, el uso de int no afecta el rendimiento, ya que el comportamiento indefinido o los desbordamientos de enteros con signo permiten al compilador promover internamente cualquier int a std::ptrdiff_t en bucles que tratan con índices, pero vemos claramente de lo anterior que el compilador sí No te sientas como en casa con int . Además, el uso de std::ptrdiff_t en una plataforma de 64 bits hará que sea menos probable que ocurran desbordamientos, ya que veo que cada vez más personas quedan atrapadas por desbordamientos int cuando tienen que lidiar con enteros mayores que 2^31 - 1 que se vuelven realmente comunes estos días.

Por lo que he visto, lo único que distingue a int parece ser el hecho de que literales como 5 son int , pero no veo dónde podría causar algún problema si pasamos a std::ptrdiff_t por defecto entero.

Estoy a punto de hacer std::ptrdiff_t como el entero estándar de facto para todo el código escrito en mi pequeña empresa. ¿Hay alguna razón por la que podría ser una mala elección?

PD: Estoy de acuerdo con el hecho de que el nombre std::ptrdiff_t es feo, razón por la cual lo escribí en il::int_t que se ve un poco mejor.

PD: Como sé que muchas personas me recomendarán usar std::size_t como un entero predeterminado, realmente quiero dejar en claro que no quiero usar un entero sin signo como mi entero predeterminado. El uso de std::size_t como un número entero predeterminado en el STL ha sido un error tal como lo reconocieron Bjarne Stroustrup y el comité estándar en el video Panel interactivo: pregúntenos cualquier cosa a las 42:38 y 1:02:50.

PD: en términos de rendimiento, en cualquier plataforma de 64 bits que conozco, + , - y * se compila de la misma manera para int y std::ptrdiff_t . Entonces no hay diferencia en la velocidad. Si divide por una constante de tiempo de compilación, la velocidad es la misma. Solo cuando divide a/b cuando no sabe nada acerca de b el uso de un entero de 32 bits en una plataforma de 64 bits le brinda una ligera ventaja en el rendimiento. Pero este caso es tan raro que no veo como una opción de alejarme de std::ptrdiff_t . Cuando tratamos con código vectorizado, aquí hay una clara diferencia, y cuanto más pequeño, mejor, pero esa es una historia diferente, y no habría razón para seguir con int . En esos casos, recomendaría ir a los tipos de tamaño fijo de C ++.


Supongo que el 99% de los casos no hay razón para usar int (o enteros con signo de otros tamaños). Sin embargo, todavía hay situaciones en las que usar int es una buena opción.

Una actuación:

Una diferencia entre int y size_t es que i++ puede ser un comportamiento indefinido para int , si i es así MAX_INT . En realidad, esto podría ser algo bueno porque el compilador podría usar este comportamiento indefinido para acelerar las cosas.

Por ejemplo, en esta question la diferencia era sobre el factor 2 entre explotar el comportamiento indefinido y usar el indicador del compilador -fwrapv que prohíbe esta explotación.

Si mi caballo de trabajo para bucle se vuelve dos veces más rápido usando int s, seguro que lo usaré

B) Menos código propenso a errores

For-loops invertidos con size_t aspecto extraño y es una fuente de errores (espero haberlo hecho bien):

for(size_t i = N-1; i < N; i--){...}

Mediante el uso

for(int i = N-1; i >= 0; i--){...}

te mereces la gratitud de los programadores de C ++ menos experimentados, que algún día tendrán que administrar tu código.

C) Diseño utilizando índices firmados

Al usar int como índices, uno podría señalar valores incorrectos / fuera de rango con valores negativos, algo que resulta útil y puede conducir a un código más claro.

  1. "buscar índice de un elemento en la matriz" podría volver -1 si el elemento no está presente. Para detectar este "error" no es necesario conocer el tamaño de la matriz.

  2. la búsqueda binaria podría devolver un índice positivo si el elemento está en la matriz, y -index para la posición donde el elemento se insertaría en la matriz (y no está en la matriz).

Claramente, la misma información podría codificarse con valores de índice positivos, pero el código se vuelve algo menos intuitivo.

Evidentemente, también hay razones para elegir int sobre std::ptrdiff_t - uno de ellos es el ancho de banda de memoria. Existen muchos algoritmos vinculados a la memoria, para ellos es importante reducir la cantidad de memoria transferida de la RAM a la memoria caché.

Si sabe, que todos los números son menores, 2^31 sería una ventaja usarlo int porque de lo contrario la mitad de la transferencia de memoria estaría escribiendo solo 0 de lo que ya sabe, que están allí.

Un ejemplo son las matrices de filas dispersas comprimidas (crs): sus índices se almacenan como ints y no long long . Debido a que muchas operaciones con matrices dispersas están vinculadas a la memoria, realmente hay una diferencia entre usar 32 o 64 bits.


En la mayoría de las arquitecturas modernas de 64 bits, int es de 4 bytes y ptrdiff_t es de 8 bytes. Si su programa usa muchos enteros, usar ptrdiff_t lugar de int podría duplicar los requisitos de memoria de su programa.

También tenga en cuenta que las CPU modernas con frecuencia se ven afectadas por el rendimiento de la memoria. El uso de enteros de 8 bytes también significa que la memoria caché de su CPU ahora tiene la mitad de elementos que antes, por lo que ahora debe esperar la memoria principal lenta con más frecuencia (lo que puede llevar fácilmente varios cientos de ciclos).

En muchos casos, el costo de la ejecución de las operaciones de "conversión de 32 a 64 bits" está completamente reducido por el rendimiento de la memoria.

Esta es una razón práctica por la que int sigue siendo popular en máquinas de 64 bits.

  • Ahora puede discutir sobre dos docenas de tipos enteros diferentes y portabilidad y comités estándar y todo, pero la verdad es que para muchos de los programas C ++ escritos, hay una arquitectura "canónica" en la que están pensando, que con frecuencia es la única. arquitectura que les preocupa. (Si está escribiendo una rutina de gráficos 3D para un juego de Windows, está seguro de que no se ejecutará en un mainframe de IBM). Entonces, para ellos, la pregunta se reduce a: "¿Necesito un número entero de 4 bytes o uno de 8 bytes aquí?

Esto está algo basado en la opinión, pero, por desgracia, la pregunta también lo plantea.

En primer lugar, se habla de números enteros e índices como si fueran lo mismo, lo cual no es el caso. Para cualquier cosa como "tipo entero, no estoy seguro de qué tamaño" , simplemente usar int es, por supuesto, la mayoría de las veces, todavía apropiado. Esto funciona bien la mayor parte del tiempo, para la mayoría de las aplicaciones, y el compilador se siente cómodo con él. Por defecto, está bien.

Para los índices de matriz, es una historia diferente.

Hasta la fecha hay una sola cosa formalmente correcta, y es std::size_t . En el futuro, puede haber un std::index_t que aclara la intención en el nivel de origen, pero hasta ahora no lo hay.
std::ptrdiff_t como un índice "funciona" pero es tan incorrecto como int ya que permite índices negativos.
Sí, esto sucede lo que el Sr. Sutter considera correcto, pero le ruego que difiera. Sí, en un nivel de instrucción en lenguaje ensamblador, esto es compatible, pero aún así me opongo. El estándar dice:

8.3.4 / 6: E1[E2] es idéntico a *((E1)+(E2)) [...] Debido a las reglas de conversión que se aplican a + , si E1 es una matriz y E2 un número entero, entonces E1[E2] refiere al miembro E2 de E1 .
5.7 / 5: [...] Si tanto el operando del puntero como el resultado apuntan a elementos del mismo objeto de matriz, o uno pasado el último elemento del objeto de matriz, [...] de lo contrario, el comportamiento es indefinido .

Una suscripción de matriz se refiere al miembro E2 de E1 . No existe un elemento negativo-th de una matriz. Pero lo más importante, la aritmética del puntero con una expresión aditiva negativa invoca un comportamiento indefinido .

En otras palabras: los índices firmados de cualquier tamaño son una elección incorrecta . Los índices no están firmados. Sí, los índices firmados funcionan , pero todavía están equivocados.

Ahora, aunque size_t es, por definición, la opción correcta (un tipo entero sin signo que es lo suficientemente grande como para contener el tamaño de cualquier objeto), puede ser discutible si es realmente una buena opción para el caso promedio, o por defecto.

Sea honesto, ¿cuándo fue la última vez que creó una matriz con 10 19 elementos?

Personalmente, utilizo unsigned int como valor predeterminado porque los 4 mil millones de elementos que esto permite son suficientes para (casi) cada aplicación, y ya empujan la computadora del usuario promedio bastante cerca de su límite (si solo suscribe una matriz de enteros, que supone 16 GB de memoria contigua asignada). Personalmente considero que el incumplimiento de los índices de 64 bits es ridículo.

Si está programando una base de datos relacional o un sistema de archivos, entonces sí, necesitará índices de 64 bits. Pero para el programa "normal" promedio, los índices de 32 bits son lo suficientemente buenos y solo consumen la mitad de almacenamiento.

Cuando mantengo alrededor de más de un puñado de índices, y si puedo pagar (porque los arreglos no son más grandes que 64k elementos), incluso bajo a uint16_t . No, no estoy bromeando allí.

¿El almacenamiento es realmente un problema? Es ridículo codiciar con dos o cuatro bytes guardados, ¿no es así? Bueno no...

El tamaño puede ser un problema para los punteros, por lo que también puede serlo para los índices. El x32 ABI no existe sin ninguna razón. No notará la sobrecarga de índices innecesariamente grandes si solo tiene un puñado de ellos en total (al igual que los punteros, de todos modos estarán en registros, nadie notará si tienen un tamaño de 4 u 8 bytes).

Pero piense, por ejemplo, en un mapa de ranuras donde almacena un índice para cada elemento (dependiendo de la implementación, dos índices por elemento). ¡Oh, diablos, seguro que hace una gran diferencia si golpeas L2 cada vez, o si tienes un error de caché en cada acceso! Más grande no siempre es mejor.

Al final del día, debe preguntarse qué paga y qué obtiene a cambio. Con eso en mente, mi recomendación de estilo sería:

Si le cuesta "nada" porque solo tiene, por ejemplo, un puntero y algunos índices para mantener, entonces use lo que es formalmente correcto (eso sería size_t ). Formalmente correcto es bueno, correcto siempre funciona, es legible e inteligible, y correcto es ... nunca está mal .

Sin embargo, si le cuesta (tiene quizás varios cientos o mil o diez mil índices), y lo que obtiene no vale nada (porque, por ejemplo, ni siquiera puede almacenar 2 20 elementos, por lo tanto, si puede suscribirse 2 32 o 2 64 no hace ninguna diferencia), debería pensar dos veces antes de ser demasiado derrochador.


Hubo una discusión sobre las Pautas principales de C ++ sobre qué usar:

https://github.com/isocpp/CppCoreGuidelines/pull/1115

Herb Sutter escribió que se agregará gsl::index (en el futuro tal vez std::index ), que se definirá como ptrdiff_t .

hsutter comentó en 26 dic 2017 •

(Gracias a muchos expertos de WG21 por sus comentarios y comentarios en esta nota).

Agregue el siguiente typedef a GSL

namespace gsl { using index = ptrdiff_t; }

y recomiende gsl::index para todos los índices / subíndices / tamaños de contenedor.

Razón fundamental

Las Directrices recomiendan utilizar un tipo firmado para subíndices / índices. Ver ES.100 a ES.107. C ++ ya usa enteros firmados para subíndices de matriz.

Queremos poder enseñar a las personas a escribir "un nuevo código limpio y moderno" que sea simple, natural, sin advertencias a niveles de advertencia altos, y que no nos haga escribir una nota al pie de página sobre el código simple.

Si no tenemos una palabra corta como index que sea competitiva con int y auto , la gente seguirá usando int y auto y obtendrá sus errores. Por ejemplo, escribirán for(int i=0; i<v.size(); ++i) o for(auto i=0; i<v.size(); ++i) que tienen 32 bits errores de tamaño en plataformas ampliamente utilizadas y for(auto i=v.size()-1; i>=0; ++i) que simplemente no funciona. No creo que podamos enseñar for(ptrdiff_t i = ... con una cara seria, o que la gente lo acepte.

Si tuviéramos un tipo aritmético saturado, podríamos usarlo. De lo contrario, la mejor opción es ptrdiff_t que tiene casi todas las ventajas de un tipo de aritmética saturado sin signo, excepto que ptrdiff_t todavía ptrdiff_t el estilo de bucle generalizado for(ptrdiff_t i=0; i<v.size(); ++i) emitir desajustes con signo / sin signo en i<v.size() (y de manera similar para i!=v.size() ) para los contenedores STL de hoy. (Si un STL futuro cambia su size_type para que se firme, incluso este último inconveniente desaparece).

Sin embargo, sería inútil (y vergonzoso) tratar de enseñar a las personas a escribir rutinariamente for (ptrdiff_t i = ... ; ... ; ...) . (Incluso las Directrices actualmente lo usan en un solo lugar, y ese es un "mal" ejemplo que no está relacionado con la indexación '').

Por lo tanto, deberíamos proporcionar gsl::index (que luego se puede proponer para su consideración como std::index ) como typedef para ptrdiff_t , para que podamos (y no vergonzosamente) enseñar a las personas a escribir rutinariamente (index i = ... ; ... ; ...) .

¿Por qué no decirle a la gente que escriba ptrdiff_t ? Porque creemos que sería vergonzoso decirle a la gente que eso es lo que tienes que hacer en C ++, e incluso si lo hiciéramos, la gente no lo hará. Escribir ptrdiff_t es demasiado feo e inaceptable en comparación con auto e int . El punto de agregar el index nombres es hacer que sea lo más fácil y atractivo posible usar un tipo con signo del tamaño correcto.

Editar: Más justificación de Herb Sutter

¿Es ptrdiff_t suficientemente grande? Sí. Ya se requiere que los contenedores estándar no tengan más elementos de los que puede representar ptrdiff_t , porque restar dos iteradores debe caber en un ptrdiff_t .

Pero, ¿es ptrdiff_t lo suficientemente grande, si tengo una matriz incorporada de caracteres o byte que es más grande que la mitad del espacio de direcciones de memoria y tiene más elementos de los que se pueden representar en un ptrdiff_t ? Sí. C ++ ya usa enteros firmados para subíndices de matriz. Por lo tanto, use index como la opción predeterminada para la gran mayoría de los usos, incluidos todos los arreglos integrados. (Si encuentra el caso extremadamente raro de una matriz, o tipo de matriz, que es mayor que la mitad del espacio de direcciones y cuyos elementos son sizeof(1) , y tiene cuidado de evitar problemas de truncamiento, continúe y use un size_t para los índices en ese contenedor muy especial solamente. Tales bestias son muy raras en la práctica, y cuando surgen a menudo no se indexarán directamente por código de usuario. Por ejemplo, generalmente surgen en un administrador de memoria que se hace cargo de la asignación del sistema y reparte las asignaciones individuales más pequeñas que usan sus usuarios, o en un MPEG o similar que proporciona su propia interfaz; en ambos casos, size_t solo debería ser necesario internamente dentro del administrador de memoria o la implementación de la clase MPEG).


La mayoría de los programas no viven y mueren al borde de unos pocos ciclos de CPU, y int es muy fácil de escribir. Sin embargo, si es sensible al rendimiento, le sugiero que use los tipos enteros de ancho fijo definidos en <cstdint> , como int32_t o uint64_t . Estos tienen el beneficio de ser muy claros en su comportamiento previsto con respecto a ser firmado o no, así como su tamaño en la memoria. Este encabezado también incluye las variantes rápidas como int_fast32_t , que tienen al menos el tamaño establecido, pero podrían ser más, si ayudan al rendimiento.


Llegué a esto desde la perspectiva de un viejo temporizador (pre C ++) ... Se entendió en el pasado que int era la palabra nativa de la plataforma y era probable que ofreciera el mejor rendimiento.

Si necesitaras algo más grande, lo usarías y pagarías el precio en rendimiento. Si necesita algo más pequeño (memoria limitada o necesidad específica de un tamaño fijo), lo mismo ... de lo contrario, use int . Y sí, si su valor estaba en el rango donde int en una plataforma de destino podría acomodarlo e int en otra plataforma de destino no podría ... entonces teníamos nuestro tiempo de compilación definido específicamente por tamaño (antes de que se estandarizaran, hicimos el nuestro).

Pero ahora, hoy en día, los procesadores y compiladores son mucho más sofisticados y estas reglas no se aplican tan fácilmente. También es más difícil predecir cuál será el impacto de rendimiento de su elección en alguna plataforma o compilador futuro desconocido ... ¿Cómo sabemos realmente que uint64_t, por ejemplo, funcionará mejor o peor que uint32_t en cualquier objetivo futuro en particular? A menos que sea un procesador / compilador gurú, no ...

Entonces ... tal vez sea anticuado, pero a menos que esté escribiendo código para un entorno restringido como Arduino, etc. Todavía uso int para valores de propósito general que sé que estarán dentro del tamaño int en todos los objetivos razonables para la aplicación que estoy escribiendo. . Y el compilador lo toma desde allí ... En estos días eso generalmente significa 32 bits firmados. Incluso si se supone que 16 bits es el tamaño entero mínimo, cubre la mayoría de los casos de uso ... y los casos de uso para números mayores que ese se identifican fácilmente y se manejan con los tipos apropiados.


Mi consejo para usted es no mirar demasiado la salida del lenguaje ensamblador, no preocuparse demasiado por el tamaño exacto de cada variable y no decir cosas como "el compilador se siente como en casa". (Realmente no sé qué quieres decir con eso último).

Para los enteros de variedades de jardín, de los que la mayoría de los programas están llenos, se supone que plain int es un buen tipo para usar. Se supone que es el tamaño de palabra natural de la máquina. Se supone que es eficiente de usar, sin desperdiciar memoria innecesaria ni inducir muchas conversiones adicionales al moverse entre la memoria y los registros de cálculo.

Ahora, es cierto que hay muchos usos más especializados para los que el int simple ya no es apropiado. En particular, los tamaños de los objetos, los recuentos de elementos y los índices en matrices casi siempre son size_t . ¡Pero eso no significa que todos los enteros deberían ser size_t !

También es cierto que las mezclas de tipos con signo y sin signo, y las mezclas de tipos de diferentes tamaños, pueden causar problemas. Pero la mayoría de ellos están bien atendidos por los compiladores modernos y las advertencias que emiten para combinaciones inseguras. Por lo tanto, siempre que utilice un compilador moderno y preste atención a sus advertencias, no necesita elegir un tipo antinatural solo para tratar de evitar problemas de desajuste de tipo.


No creo que haya una razón real para usar int .

¿Cómo elegir el tipo entero?

  • Si es para operaciones de bit, puede usar un tipo sin signo, de lo contrario use uno con signo
  • Si es para algo relacionado con la memoria (índice, tamaño del contenedor, etc.), para el cual no conoce el límite superior, use std::ptrdiff_t (el único problema es cuando el tamaño es mayor que PTRDIFF_MAX , lo cual es raro en práctica)
  • De lo contrario, use intXX_t o int(_least)/(_fast)XX_t .

Estas reglas cubren todos los usos posibles de int y ofrecen una mejor solución:

  • int no es bueno para almacenar cosas relacionadas con la memoria, ya que su rango puede ser más pequeño de lo que puede ser un índice (esto no es algo teórico: para máquinas de 64 bits, int suele ser de 32 bits, por lo que con int , solo puede manejar 2 mil millones de elementos)
  • int no es bueno para almacenar enteros "generales", ya que su rango puede ser más pequeño de lo necesario (el comportamiento indefinido ocurre si el rango no es suficiente), o por el contrario, su rango puede ser mucho mayor de lo necesario (por lo que se desperdicia memoria)

La única razón por la que uno podría usar un int , si hace un cálculo, y sabe que el rango encaja en [-32767; 32767] (el estándar solo garantiza este rango. Sin embargo, tenga en cuenta que las implementaciones son libres de proporcionar int s de mayor tamaño, y generalmente lo hacen. Actualmente, int es de 32 bits en muchas plataformas).

Como los tipos std mencionados son un poco tediosos de escribir, uno podría escribirlos para que sean más cortos (uso s8 / u8 /.../ s64 / u64 y spt / upt ("tipo de tamaño de puntero (sin signo)") para ptrdiff_t / size_t . He estado usando estos typedefs durante 15 años, y nunca he escrito un int desde entonces ...).


No hay razón formal para usar int . No corresponde a nada cuerdo según el estándar. Para los índices, casi siempre desea un entero de tamaño de puntero con signo.

Dicho esto, escribir int siente como si std::ptrdiff_t decirle hola a Ritchie y escribir std::ptrdiff_t siente como si Stroustrup te std::ptrdiff_t pateado el trasero. Los codificadores también son personas, no traigan demasiada fealdad a sus vidas. Preferiría usar typedef like index long o algo fácil de escribir en lugar de std::ptrdiff_t .