Hoje me deparei com um ponto interessante em uma revisão sobre o Code Review . O @Veedrac recomendou nesta resposta que tipos de tamanho variável (por exemplo, int
e long
) sejam substituídos por tipos de tamanho fixo como uint64_t
e uint32_t
. Citação dos comentários dessa resposta:
Os tamanhos de int e long (e, portanto, os valores que eles podem conter) dependem da plataforma. Por outro lado, int32_t sempre tem 32 bits. Usar int apenas significa que seu código funciona de maneira diferente em plataformas diferentes, o que geralmente não é o que você deseja.
O raciocínio por trás do padrão que não corrige os tipos comuns é parcialmente explicado aqui por @supercat. C foi escrito para ser portátil em todas as arquiteturas, ao contrário da montagem que geralmente era usada para programação de sistemas na época.
Eu acho que a intenção do projeto era originalmente que cada tipo, exceto int, fosse a menor coisa que pudesse lidar com números de vários tamanhos, e que esse seria o tamanho "de uso geral" mais prático que poderia lidar com +/- 32767.
Quanto a mim, sempre usei int
e não me preocupo com as alternativas. Eu sempre pensei que é o tipo mais com melhor desempenho, final da história. O único lugar que achei que a largura fixa seria útil é ao codificar dados para armazenamento ou transferência em uma rede. Eu raramente vi tipos de largura fixa no código escrito por outras pessoas também.
Estou preso nos anos 70 ou existe realmente uma justificativa para usar int
na era do C99 e além?
fonte
Respostas:
Existe um mito comum e perigoso de que tipos como
uint32_t
salvar programadores de terem que se preocupar com o tamanho deint
. Embora seja útil que o Comitê de Padrões defina um meio de declarar números inteiros com semântica independente de máquina, tipos não assinados comouint32_t
semântica são muito frouxos para permitir que o código seja escrito de maneira limpa e portátil; além disso, tipos assinados comoint32
semântica são, para muitas aplicações, formas definidas de forma desnecessária e, portanto, impedem o que de outra forma seriam otimizações úteis.Considere, por exemplo:
Em máquinas nas quais
int
não é possível conter 4294967295 ou 18446744065119617025, a primeira função será definida para todos os valores den
eexponent
, e seu comportamento não será afetado pelo tamanho deint
; além disso, o padrão não exigirá que ele produza comportamento diferente em máquinas com qualquer tamanho deint
Alguns valores den
eexponent
, no entanto, fará com que invoque o Comportamento indefinido em máquinas nas quais 4294967295 é representável comoint
18446744065119617025, mas não é.A segunda função produzirá Comportamento indefinido para alguns valores de
n
eexponent
em máquinas ondeint
não pode conter 4611686014132420609, mas produzirá um comportamento definido para todos os valores den
eexponent
em todas as máquinas em que puder (as especificaçõesint32_t
implicam que o comportamento de quebra de complemento de dois em máquinas onde é menor queint
).Historicamente, mesmo que o Padrão não dissesse nada sobre o que os compiladores deveriam fazer com o
int
transbordamentoupow
, os compiladores teriam consistentemente produzido o mesmo comportamento como seint
tivesse sido grande o suficiente para não transbordar. Infelizmente, alguns compiladores mais recentes podem tentar "otimizar" programas, eliminando comportamentos não exigidos pelo Padrão.fonte
pow
, lembre-se de que este código é apenas um exemplo e não serveexponent=0
!exponent=1
, resultará em n sendo multiplicado por si só uma vez, uma vez que o decremento é realizado após a verificação, se o incremento for realizado antes da verificação ( ex. - expoente), nenhuma multiplicação será realizada e n será retornado.N^(2^exponent)
, mas os cálculos do formulárioN^(2^exponent)
são frequentemente usados no cálculo das funções de exponenciação, e a exponenciação mod-4294967296 é útil para coisas como calcular o hash da concatenação de duas seqüências de caracteres cuja hashes são conhecidos.uint32_t
por 31 nunca produzirá UB, mas a eficiência A maneira de calcular 31 ^ N envolve cálculos de 31 ^ (2 ^ N), o que será necessário.int32_t
às vezes, tendo definido o estouro e às vezes não, que é o que você parece estar mencionando, parece ter uma importância mínima em relação ao fato de que isso me permite pensar em como evitar o estouro. E se você deseja um estouro definido, é provável que esteja querendo que o módulo de resultado tenha algum valor fixo - portanto, você está usando tipos de largura fixa de qualquer maneira.Para valores intimamente relacionados a ponteiros (e, portanto, à quantidade de memória endereçável), como tamanhos de buffer, índices de matriz e Windows '
lParam
, faz sentido ter um tipo inteiro com um tamanho dependente da arquitetura. Portanto, tipos de tamanho variável ainda são úteis. É por isso que temos os typedefssize_t
,ptrdiff_t
,intptr_t
, etc. Eles têm que ser typedefs porque nenhum dos built-in C inteiro tipos precisa ser ponteiro porte.Então a questão é realmente se
char
,short
,int
,long
, elong long
ainda são úteis.IME, ainda é comum os programas C e C ++ usarem
int
para a maioria das coisas. E na maioria das vezes (ou seja, quando seus números estão na faixa de ± 32.767 e você não possui requisitos rigorosos de desempenho), isso funciona perfeitamente.Mas e se você precisar trabalhar com números no intervalo de 17 a 32 bits (como as populações das grandes cidades)? Você poderia usar
int
, mas isso codificaria uma dependência de plataforma. Se você quiserlong
seguir rigorosamente o padrão, poderá usar o que é garantido em pelo menos 32 bits.O problema é que o padrão C não especifica nenhum tamanho máximo para um tipo inteiro. Existem implementações nas quais
long
são 64 bits, o que duplica o uso de memória. E se esseslong
elementos forem elementos de uma matriz com milhões de itens, você estragará a memória como um louco.Portanto,
int
nemlong
é um tipo adequado para usar aqui se você deseja que seu programa seja multiplataforma e com eficiência de memória. Enterint_least32_t
.long
, evitando os problemas de truncamento doint
int
, evitando a perda de memória do 64 bitslong
.int
OTOH, suponha que você não precise de grandes números ou matrizes enormes, mas precisa de velocidade. E
int
pode ser grande o suficiente em todas as plataformas, mas não é necessariamente o tipo mais rápido: os sistemas de 64 bits geralmente ainda têm 32 bitsint
. Mas você pode usarint_fast16_t
e obter o tipo “mais rápido”, se éint
,long
oulong long
.Portanto, existem casos de uso práticos para os tipos de
<stdint.h>
. Os tipos inteiros padrão não significam nada. Especialmentelong
, que pode ter 32 ou 64 bits e pode ou não ser grande o suficiente para conter um ponteiro, dependendo do capricho dos escritores do compilador.fonte
uint_least32_t
é que suas interações com outros tipos são especificadas de maneira ainda mais fraca do que as deuint32_t
. IMHO, o Padrão deve definir tipos comouwrap32_t
eunum32_t
, com a semântica que qualquer compilador que define o tipouwrap32_t
, deve promover como um tipo não assinado, essencialmente nos mesmos casos em que seria promovido seint
fossem 32 bits, e qualquer compilador que define o tipounum32_t
deve garantir que promoções aritméticas básicas sempre o convertem em um tipo assinado capaz de manter seu valor.intN_t
euintN_t
, e cujos comportamentos definidos seriam consistentes comintN_t
euintN_t
, mas que dariam aos compiladores alguma liberdade no caso de códigos atribuídos a valores fora de seu intervalo [permitindo semânticas semelhantes àquelas talvez pretendidouint_least32_t
, mas sem incertezas, como adicionar umuint_least16_t
e umint32_t
produziria um resultado assinado ou não assinado.