No mundo C ++ (ou C) de plataforma cruzada de hoje, temos :
Data model | short | int | long | long long | pointers/size_t | Sample operating systems
...
LLP64/IL32P64 16 32 32 64 64 Microsoft Windows (x86-64 and IA-64)
LP64/I32LP64 16 32 64 64 64 Most Unix and Unix-like systems, e.g. Solaris, Linux, BSD, and OS X; z/OS
...
O que isso significa hoje é que, para qualquer número inteiro "comum" (assinado), int
será suficiente e ainda poderá ser usado como o tipo inteiro padrão ao escrever o código do aplicativo C ++. Também - para propósitos práticos atuais - terá um tamanho consistente entre as plataformas.
Se um caso de uso requer pelo menos 64 bits, podemos usar hoje long long
, embora possivelmente use um dos tipos de especificação de testemunha ou o __int64
tipo possa fazer mais sentido.
Isso deixa long
no meio, e estamos considerando proibir completamente o uso de long
nosso código de aplicativo .
Isso faria sentido ou existe um caso para usar long
no código C ++ (ou C) moderno que precisa executar plataforma cruzada? (plataforma sendo desktop, dispositivos móveis, mas não itens como microcontroladores, DSPs etc.)
Links de fundo possivelmente interessantes:
- O que o padrão C ++ indica o tamanho do tipo int, long?
- Por que a equipe do Win64 escolheu o modelo LLP64?
- Modelos de programação de 64 bits: Por que LP64? (um pouco envelhecido)
- É
long
garantido que tenha pelo menos 32 bits? (Isso aborda a discussão de comentários abaixo. Resposta .)
fonte
long
é a única maneira de garantir 32 bits.int
pode ter 16 bits, portanto, para algumas aplicações, não é suficiente. Sim,int
às vezes são 16 bits em compiladores modernos. Sim, as pessoas escrevem software em microcontroladores. Eu diria mais pessoas escrever software que tem mais usuários em microcontroladores do que no PC com o surgimento de dispositivos iPhone e Android para não mencionar o surgimento de Arduinos etc.int
ainda existem 16 bits. Detesto dizer isso, mas se você escrever sobre o "mundo entre plataformas de hoje", não poderá ignorar todo o subcontinente indiano.Respostas:
O único motivo que eu usaria
long
hoje é ao chamar ou implementar uma interface externa que a utilize.Como você diz em seu post, short e int têm características razoavelmente estáveis em todas as principais plataformas de desktop / servidor / celular hoje e não vejo razão para isso mudar no futuro próximo. Então, vejo poucas razões para evitá-las em geral.
long
por outro lado, está uma bagunça. Em todos os sistemas de 32 bits, estou ciente de que tinha as seguintes características.Grandes quantidades de código foram gravadas com base em uma ou mais dessas características. No entanto, com a mudança para 64 bits, não foi possível preservar todos eles. As plataformas tipo Unix foram para o LP64, que preservou as características 2 e 3 ao custo da característica 1. O Win64 foi para o LLP64, que preservou a característica 1 ao custo das características 2 e 3. O resultado é que você não pode mais confiar em nenhuma dessas características e que a IMO deixa poucas razões para usar
long
.Se você deseja um tipo com tamanho exatamente de 32 bits, use
int32_t
.Se você deseja um tipo que seja do mesmo tamanho que um ponteiro, use
intptr_t
(ou melhoruintptr_t
).Se você deseja um tipo que é o maior item que pode ser trabalhado em um único registro / instrução, infelizmente não acho que o padrão forneça um.
size_t
deve estar certo nas plataformas mais comuns, mas não no x32 .PS
Eu não me incomodaria com os tipos "rápido" ou "menos". Os tipos "menos" são importantes apenas se você se preocupa com a portabilidade para realmente obscurecer arquiteturas onde
CHAR_BIT != 8
. O tamanho dos tipos "rápidos" na prática parece bastante arbitrário. O Linux parece torná-los pelo menos do mesmo tamanho que o ponteiro, o que é bobo em plataformas de 64 bits com suporte rápido de 32 bits, como x86-64 e arm64. O IIRC iOS os torna o menor possível. Não tenho certeza do que outros sistemas fazem.PPS
Um motivo para usar
unsigned long
(mas não é clarolong
) é porque é garantido o comportamento do módulo. Infelizmente, devido às regras de promoção erradas de C, os tipos não assinados são menores que oint
comportamento do módulo.Hoje, em todas as principais plataformas,
uint32_t
é do mesmo tamanho ou maior que o int e, portanto, possui um comportamento de módulo. No entanto, tem havido historicamente e, teoricamente, pode haver nas plataformas futurasint
64 bits e, portantouint32_t
, não possui comportamento de módulo.Pessoalmente, eu diria que é melhor entrar no hábito de forçar o comportamento do módulo usando "1u *" ou "0u +" no início de suas equações, pois isso funcionará para qualquer tamanho de tipo não assinado.
fonte
Como você mencionou na sua pergunta, o software moderno é sobre interoperar entre plataformas e sistemas na Internet. Os padrões C e C ++ fornecem intervalos para tamanhos de tipo inteiro, não tamanhos específicos (em contraste com linguagens como Java e C #).
Para garantir que seu software compilado em plataformas diferentes funcione com os mesmos dados da mesma maneira e para garantir que outro software possa interagir com o software usando os mesmos tamanhos, você deve usar números inteiros de tamanho fixo.
Digite o
<cstdint>
que fornece exatamente isso e é um cabeçalho padrão que todas as plataformas de compilador e biblioteca padrão devem fornecer. Nota: esse cabeçalho era necessário apenas a partir do C ++ 11, mas muitas implementações de bibliotecas mais antigas o forneciam de qualquer maneira.Deseja um número inteiro não assinado de 64 bits? Use
uint64_t
. Número inteiro de 32 bits assinado? Useint32_t
. Embora os tipos no cabeçalho sejam opcionais, as plataformas modernas devem suportar todos os tipos definidos nesse cabeçalho.Às vezes, é necessária uma largura de bit específica, por exemplo, em uma estrutura de dados usada para comunicação com outros sistemas. Outras vezes não é. Para situações menos rigorosas,
<cstdint>
fornece tipos com largura mínima.Existem menos variantes:
int_leastXX_t
será um tipo inteiro com no mínimo XX bits. Ele usará o menor tipo que fornece XX bits, mas é permitido que o tipo seja maior que o número especificado de bits. Na prática, estes são tipicamente os mesmos que os tipos descritos acima que fornecem o número exato de bits.Também existem variantes rápidas :
int_fastXX_t
tem pelo menos XX bits, mas deve usar um tipo que tenha desempenho rápido em uma plataforma específica. A definição de "rápido" neste contexto não é especificada. No entanto, na prática, isso normalmente significa que um tipo menor que o tamanho de registro de uma CPU pode ser alias para um tipo de tamanho de registro de CPU. Por exemplo, o cabeçalho do Visual C ++ 2015 especifica queint_fast16_t
é um número inteiro de 32 bits porque a aritmética de 32 bits é geralmente mais rápida em x86 do que a aritmética de 16 bits.Isso tudo é importante porque você deve poder usar tipos que possam conter os resultados dos cálculos que seu programa executa independentemente da plataforma. Se um programa produz resultados corretos em uma plataforma, mas resultados incorretos em outra devido a diferenças no excesso de número inteiro, isso é ruim. Ao usar os tipos de número inteiro padrão, você garante que os resultados em diferentes plataformas serão os mesmos em relação ao tamanho dos números inteiros usados (é claro que pode haver outras diferenças entre as plataformas além da largura do número inteiro).
Então, sim,
long
deve ser banido do código C ++ moderno. Assim seint
,short
elong long
.fonte
std
namespace quando#include
d em uma unidade de compilação C ++, mas a documentação que vinculei não menciona isso e o Visual Studio parece não se importar com o acesso a eles.int
pode ser ... excessiva? (Eu consideraria isso se o código precisar ser extremamente portátil em todas as plataformas obscuras (e não tão obscuras). A proibição de "código do aplicativo" pode não se encaixar muito bem com nossos desenvolvedores.#include <cstdint>
é obrigado a inserir os tiposstd::
e (infelizmente) opcionalmente também é permitido colocá-los no espaço de nomes global.#include <stdint.h>
é exatamente o contrário. O mesmo se aplica a qualquer outro par de cabeçalhos C. Veja: stackoverflow.com/a/13643019/2757035 Eu gostaria que o Padrão exigisse que cada um apenas afetasse seu respectivo espaço de nome necessário - em vez de aparentemente se curvar às convenções precárias estabelecidas por algumas implementações - mas tudo bem, aqui estamos.Não, banir os tipos inteiros internos seria absurdo. Eles também não devem ser abusados.
Se você precisar de um número inteiro com exatamente N bits de largura, use (ou se precisar de uma versão). Pensar como um número inteiro de 32 bits e como um número inteiro de 64 bits está errado. Pode acontecer que seja assim em suas plataformas atuais, mas isso depende do comportamento definido pela implementação.
std::intN_t
std::uintN_t
unsigned
int
long long
O uso de tipos inteiros de largura fixa também é útil para interoperar com outras tecnologias. Por exemplo, se algumas partes do seu aplicativo são escritas em Java e outras em C ++, você provavelmente desejará corresponder os tipos inteiros para obter resultados consistentes. (Ainda esteja ciente de que o estouro em Java possui semântica bem definida, enquanto o
signed
estouro em C ++ é um comportamento indefinido, portanto a consistência é um objetivo alto.) Eles também serão inestimáveis ao trocar dados entre diferentes hosts de computação.Se você não precisa exatamente de N bits, mas apenas de um tipo amplo o suficiente , considere usar (otimizado para espaço) ou (otimizado para velocidade). Novamente, ambas as famílias também têm contrapartes.
std::int_leastN_t
std::int_fastN_t
unsigned
Então, quando usar os tipos incorporados? Bem, como o padrão não especifica sua largura com precisão, use-os quando não se importar com a largura real do bit, mas com outras características.
A
char
é o menor número inteiro endereçável pelo hardware. O idioma realmente obriga a usá-lo para aliasing de memória arbitrária. Também é o único tipo viável para representar cadeias de caracteres (estreitas).Um
int
normalmente será o tipo mais rápido a máquina pode manipular. Ele será amplo o suficiente para poder ser carregado e armazenado com uma única instrução (sem ter que mascarar ou trocar bits) e estreito o suficiente para poder ser operado com as instruções de hardware mais eficientes. Portanto,int
é uma escolha perfeita para transmitir dados e fazer aritmética quando o estouro não é uma preocupação. Por exemplo, o tipo subjacente padrão de enumerações éint
. Não altere para um número inteiro de 32 bits apenas porque você pode. Além disso, se você tiver um valor que possa ser apenas –1, 0 e 1, umint
é uma escolha perfeita, a menos que você armazene grandes matrizes delas. Nesse caso, convém usar um tipo de dados mais compacto ao custo de pagar um preço mais alto pelo acesso a elementos individuais. Um cache mais eficiente provavelmente pagará por isso. Muitas funções do sistema operacional também são definidas em termos deint
. Seria tolice converter seus argumentos e resultados para frente e para trás. Tudo o que isso poderia fazer é introduzir erros de estouro.long
geralmente será o tipo mais amplo que pode ser manuseado com instruções de máquina única. Isso é especialmenteunsigned long
atraente para lidar com dados brutos e todo tipo de manipulação de bits. Por exemplo, eu esperaria verunsigned long
na implementação de um vetor de bits. Se o código for escrito com cuidado, não importa a largura do tipo (porque o código se adaptará automaticamente). Nas plataformas em que a palavra-máquina nativa é de 32 bits, a matriz de backup do vetor de bits é uma matriz deunsigned
Inteiros de 32 bits é o mais desejável, porque seria tolo usar um tipo de 64 bits que precisa ser carregado por instruções caras apenas para mudar e mascarar os bits desnecessários novamente. Por outro lado, se o tamanho da palavra nativa da plataforma for de 64 bits, desejo uma matriz desse tipo, porque isso significa que operações como "encontrar o primeiro conjunto" podem ser executadas duas vezes mais rápido. Portanto, o “problema” dolong
tipo de dados que você está descrevendo, que seu tamanho varia de plataforma para plataforma, na verdade é um recurso que pode ser utilizado de maneira adequada. Isso só se torna um problema se você pensar nos tipos incorporados como tipos de uma certa largura de bit, o que eles simplesmente não são.char
,int
Elong
são tipos muito úteis como descrito acima.short
elong long
não são tão úteis porque sua semântica é muito menos clara.fonte
long
entre o Windows e o Unix. Eu posso estar entendendo mal, mas sua descrição da diferença no tamanho delong
ser um "recurso" em vez de um "problema" faz sentido para mim ao comparar modelos de dados de 32 e 64 bits, mas não para essa comparação específica. No caso específico desta pergunta, isso é realmente um recurso? Ou é um recurso em outras situações (isto é, em geral) e inofensivo neste caso?uint32_t
valores serão realizados como assinado ,int
aritmética -width em uma plataforma ondeint
é mais amplo do queuint32_t
. (Com ABIs de hoje, isso é esmagadoramente mais provável que seja um problema parauint16_t
.)long
normalmente será o tipo mais amplo que pode ser manuseado com instruções de máquina única. ..." - e isso é exatamente errado . Veja o modelo de dados do Windows. IMHO, todo o exemplo a seguir se divide, porque em x64 o Windows ainda é de 32 bits.Outra resposta já foi elaborada sobre os tipos cstdint e variações menos conhecidas.
Eu gostaria de acrescentar a isso:
use nomes de tipos específicos de domínio
Ou seja, não declare seus parâmetros e variáveis
uint32_t
(certamente nãolong
!), Mas nomes comochannel_id_type
,room_count_type
etc.sobre bibliotecas
Bibliotecas de terceiros que usam
long
ou outros enfeites podem ser irritantes, especialmente se usadas como referências ou indicadores para elas.A melhor coisa é fazer invólucros.
Minha estratégia, em geral, é criar um conjunto de funções semelhantes a cast que serão usadas. Eles estão sobrecarregados para aceitar apenas os tipos que correspondem exatamente aos tipos correspondentes, juntamente com as variações de ponteiro etc. necessárias. Eles são definidos especificamente para o os / compiler / settings. Isso permite remover avisos e garantir que apenas as conversões "corretas" sejam usadas.
Em particular, com diferentes tipos primitivos produzindo 32 bits, sua escolha de como
int32_t
é definida pode não corresponder à chamada da biblioteca (por exemplo, int vs long no Windows).A função de conversão de documentos documenta o conflito, fornece uma verificação em tempo de compilação do resultado correspondente ao parâmetro da função e remove qualquer aviso ou erro se e somente se o tipo real corresponder ao tamanho real envolvido. Ou seja, ele está sobrecarregado e definido se eu passar (no Windows) um
int*
oulong*
ae fornecer um erro em tempo de compilação.Portanto, se a biblioteca for atualizada ou alguém alterar o que
channel_id_type
é, isso continuará sendo verificado.fonte