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.
Esta pregunta ya tiene una respuesta aquí:
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.
-
"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. -
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+
, siE1
es una matriz yE2
un número entero, entoncesE1[E2]
refiere al miembroE2
deE1
.
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 conint
yauto
, la gente seguirá usandoint
yauto
y obtendrá sus errores. Por ejemplo, escribiránfor(int i=0; i<v.size(); ++i)
ofor(auto i=0; i<v.size(); ++i)
que tienen 32 bits errores de tamaño en plataformas ampliamente utilizadas yfor(auto i=v.size()-1; i>=0; ++i)
que simplemente no funciona. No creo que podamos enseñarfor(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 queptrdiff_t
todavíaptrdiff_t
el estilo de bucle generalizadofor(ptrdiff_t i=0; i<v.size(); ++i)
emitir desajustes con signo / sin signo eni<v.size()
(y de manera similar parai!=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 comostd::index
) como typedef paraptrdiff_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á. Escribirptrdiff_t
es demasiado feo e inaceptable en comparación conauto
eint
. El punto de agregar elindex
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 representarptrdiff_t
, porque restar dos iteradores debe caber en unptrdiff_t
.Pero, ¿es
ptrdiff_t
lo suficientemente grande, si tengo una matriz incorporada de caracteres obyte
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 unptrdiff_t
? Sí. C ++ ya usa enteros firmados para subíndices de matriz. Por lo tanto, useindex
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 sonsizeof(1)
, y tiene cuidado de evitar problemas de truncamiento, continúe y use unsize_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 quePTRDIFF_MAX
, lo cual es raro en práctica) -
De lo contrario, use
intXX_t
oint(_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 conint
, 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
algo fácil de escribir en lugar de
long
o
std::ptrdiff_t
.