Eu tenho uma pergunta muito simples que me confunde por um longo tempo. Como estou lidando com redes e bancos de dados, muitos dados são contadores de 32 e 64 bits (não assinados), IDs de identificação de 32 e 64 bits (também não possuem mapeamento significativo para sinal). Praticamente nunca lido com qualquer palavra real que possa ser expressa como um número negativo.
Eu e meus colegas de trabalho usamos rotineiramente tipos não assinados como uint32_t
e uint64_t
para esses assuntos e, como acontece com muita frequência, também os usamos para índices de matriz e outros usos inteiros comuns.
Ao mesmo tempo, vários guias de codificação que estou lendo (por exemplo, Google) desencorajam o uso de tipos inteiros não assinados e, tanto quanto sei, nem Java nem Scala possuem tipos inteiros não assinados.
Portanto, não consegui descobrir o que é certo: usar valores assinados em nosso ambiente seria muito inconveniente, ao mesmo tempo em que codificamos guias para insistir em fazer exatamente isso.
fonte
Respostas:
Existem duas escolas de pensamento sobre isso, e nenhuma delas jamais concordará.
O primeiro argumenta que existem alguns conceitos que são inerentemente não assinados - como índices de matriz. Não faz sentido usar números assinados para aqueles, pois isso pode levar a erros. Ele também pode impor limites desnecessários às coisas - uma matriz que usa índices assinados de 32 bits pode acessar apenas 2 bilhões de entradas, enquanto a mudança para números de 32 bits não assinados permite 4 bilhões de entradas.
O segundo argumenta que, em qualquer programa que use números não assinados, mais cedo ou mais tarde, você terminará fazendo uma aritmética mista com sinal sem sinal. Isso pode gerar resultados estranhos e inesperados: converter um grande valor não assinado em assinado dá um número negativo e, inversamente, converter um número negativo em não assinado dá um grande número positivo. Isso pode ser uma grande fonte de erros.
fonte
int
é mais curto para digitar :)int
é mais do que suficiente para índices de matriz em 99,99% das vezes. Os problemas aritméticos assinados e não assinados são muito mais comuns e, portanto, têm precedência em termos do que evitar. Sim, os compiladores o alertam sobre isso, mas quantos avisos você recebe ao compilar qualquer projeto considerável? Ignorar avisos é perigoso e é uma prática ruim, mas no mundo real ...size_t
, a menos que haja um caso especial boa razão caso contrário.Em primeiro lugar, a diretriz de codificação do Google C ++ não é muito boa a seguir: evita coisas como exceções, impulso, etc., que são os grampos do C ++ moderno. Em segundo lugar, apenas porque uma determinada diretriz funciona para a empresa X não significa que será a opção certa para você. Eu continuaria usando tipos não assinados, pois você precisa deles.
Uma regra
int
prática decente para C ++ é: prefira, a menos que você tenha um bom motivo para usar outra coisa.fonte
return false
se essa invariante não for estabelecida. Portanto, você pode separar as coisas e usar as funções init para seus objetos, ou pode executar umstd::runtime_error
, deixar o desenrolar da pilha acontecer e permitir que todos os seus objetos RAII se limpem automaticamente e você, o desenvolvedor, possa lidar com a exceção onde for conveniente. você faz isso.nullptr
? retornar um objeto "padrão" (o que isso possa significar)? Você não resolveu nada - acabou de esconder o problema debaixo de um tapete e espera que ninguém descubra.signal(6)
? Se você usar uma exceção, os 50% dos desenvolvedores que souberem lidar com eles poderão escrever um bom código e o restante poderá ser realizado por seus colegas.As outras respostas não têm exemplos do mundo real, então adicionarei um. Uma das razões pelas quais eu (pessoalmente) tento evitar tipos não assinados.
Considere usar size_t padrão como um índice de matriz:
Ok, perfeitamente normal. Em seguida, considere que decidimos alterar a direção do loop por algum motivo:
E agora não funciona. Se
int
usássemos como iterador, não haveria problema. Eu já vi esse erro duas vezes nos últimos dois anos. Uma vez que aconteceu na produção e foi difícil de depurar.Outro motivo para mim são avisos irritantes, que fazem você escrever algo assim toda vez :
Essas são coisas menores, mas somadas. Eu sinto que o código é mais limpo se apenas números inteiros assinados são usados em todos os lugares.
Edit: Claro, os exemplos parecem idiotas, mas eu vi pessoas cometendo esse erro. Se existe uma maneira tão fácil de evitá-lo, por que não usá-lo?
Ao compilar o seguinte trecho de código com o VS2015 ou o GCC, não vejo avisos com as configurações de aviso padrão (mesmo com -Wall for GCC). Você precisa solicitar ao -Wextra para receber um aviso sobre isso no GCC. Esse é um dos motivos pelos quais você deve sempre compilar com o Wall e o Wextra (e usar o analisador estático), mas em muitos projetos da vida real as pessoas não fazem isso.
fonte
for (size_t i = n - 1; i < n; --i)
fizeram funcionar corretamente.size_t
inverso, há uma diretriz de codificação no estilo defor (size_t revind = 0u; revind < n; ++revind) { size_t ind = n - 1u - revind; func(ind); }
int
? :)int
seja grande o suficiente para armazenar todos os valores válidos desize_t
. Particularmente,int
pode permitir números apenas até 2 ^ 15-1, e geralmente o faz em sistemas que possuem limites de alocação de memória de 2 ^ 16 (ou, em certos casos, até mais altos).long
pode ser uma aposta mais segura, embora ainda não esteja garantida que funcione. Apenassize_t
é garantido que funcione em todas as plataformas e em todos os casos.O problema aqui é que você escreveu o loop de uma maneira pouco inteligente, levando a um comportamento incorreto. A construção do loop é como os iniciantes ensinam para tipos assinados (o que é correto e correto), mas simplesmente não se encaixa em valores não assinados. Mas isso não pode servir como contra-argumento contra o uso de tipos não assinados, a tarefa aqui é simplesmente acertar seu loop. E isso pode ser facilmente corrigido para funcionar de maneira confiável para tipos não assinados, como:
Essa alteração simplesmente reverte a sequência da operação de comparação e decremento e, na minha opinião, é a maneira mais eficaz, imperturbável, limpa e curta de lidar com contadores não assinados em loops invertidos. Você faria a mesma coisa (intuitivamente) ao usar um loop while:
Nenhum subfluxo pode ocorrer, o caso de um contêiner vazio é coberto implicitamente, como na variante conhecida do loop de contador assinado, e o corpo do loop pode permanecer inalterado em comparação com um contador assinado ou um loop de avanço. Você só precisa se acostumar com a construção de loop de aparência um tanto estranha. Mas depois de ver isso uma dúzia de vezes, não há mais nada ininteligível.
Eu teria sorte se os cursos para iniciantes mostrassem não apenas o loop correto para tipos assinados, mas também para tipos não assinados. Isso evitaria alguns erros que devem ser atribuídos aos desenvolvedores inconscientes em vez de culpar o tipo não assinado.
HTH
fonte
Inteiros não assinados estão lá por um motivo.
Considere, por exemplo, entregar dados como bytes individuais, por exemplo, em um pacote de rede ou em um buffer de arquivo. Ocasionalmente, você pode encontrar animais como números inteiros de 24 bits. Desloque facilmente os bits de três números inteiros não assinados de 8 bits, não tão fácil com números inteiros assinados de 8 bits.
Ou pense em algoritmos usando tabelas de pesquisa de caracteres. Se um caractere for um número inteiro não assinado de 8 bits, você poderá indexar uma tabela de pesquisa por um valor de caractere. No entanto, o que você faz se a linguagem de programação não suportar números inteiros não assinados? Você teria índices negativos para uma matriz. Bem, acho que você poderia usar algo assim,
charval + 128
mas isso é simplesmente feio.De fato, muitos formatos de arquivo usam números inteiros não assinados e, se a linguagem de programação do aplicativo não suportar números inteiros não assinados, isso pode ser um problema.
Em seguida, considere os números de sequência TCP. Se você escrever qualquer código de processamento TCP, definitivamente desejará usar números inteiros não assinados.
Às vezes, a eficiência é tão importante que você realmente precisa desse bit extra de números inteiros não assinados. Considere, por exemplo, dispositivos de IoT que são enviados em milhões. Muitos recursos de programação podem ser justificados para serem gastos em micro-otimizações.
Eu argumentaria que a justificativa para evitar o uso de tipos inteiros não assinados (aritmética de sinais mistos, comparações de sinais mistos) pode ser superada por um compilador com avisos adequados. Esses avisos geralmente não são ativados por padrão, mas veja, por exemplo,
-Wextra
ou separadamente-Wsign-compare
(ativado automaticamente em C por-Wextra
, embora eu não ache que seja ativado automaticamente em C ++) e-Wsign-conversion
.No entanto, em caso de dúvida, use um tipo assinado. Muitas vezes, é uma escolha que funciona bem. E ative esses avisos do compilador!
fonte
Existem muitos casos em que números inteiros não representam números, mas, por exemplo, uma máscara de bit, um ID etc. Basicamente, casos em que adicionar 1 a um número inteiro não tem nenhum resultado significativo. Nesses casos, use não assinado.
Existem muitos casos em que você faz aritmética com números inteiros. Nesses casos, use números inteiros assinados para evitar mau comportamento em torno de zero. Veja muitos exemplos com loops, nos quais a execução de um loop até zero usa código muito pouco intuitivo ou é interrompida devido ao uso de números não assinados. Existe o argumento "mas os índices nunca são negativos" - com certeza, mas as diferenças de índices, por exemplo, são negativas.
No caso muito raro em que os índices excedem 2 ^ 31, mas não 2 ^ 32, você não usa números inteiros não assinados, usa números inteiros de 64 bits.
Finalmente, uma boa armadilha: em um loop "for (i = 0; i <n; ++ i) a [i] ..." se i não tiver 32 bits e a memória exceder os endereços de 32 bits, o compilador não pode otimizar o acesso a um [i] incrementando um ponteiro, porque em i = 2 ^ 32 - 1 eu envolvo. Mesmo quando n nunca fica tão grande. O uso de números inteiros assinados evita isso.
fonte
Por fim, encontrei uma resposta muito boa aqui: "Secure Programming Cookbook", de J.Viega e M.Messier ( http://shop.oreilly.com/product/9780596003944.do )
Problemas de segurança com números inteiros assinados:
Há problemas com as conversões <-> não assinadas assinadas, portanto, não é aconselhável usar o mix.
fonte