Estou escrevendo um aplicativo em c para um STM32F105, usando o gcc.
No passado (com projetos mais simples), eu sempre variáveis definidas como char
, int
, unsigned int
e assim por diante.
Eu vejo que é comum usar os tipos definidos no stdint.h, tais como int8_t
, uint8_t
, uint32_t
, etc. Isto é verdade em múltiplos API do que estou usando, e também na biblioteca ARM CMSIS de ST.
Eu acredito que entendo por que devemos fazê-lo; para permitir que o compilador otimize melhor o espaço na memória. Espero que possa haver razões adicionais.
No entanto, devido às regras de promoção de número inteiro de c, continuo correndo contra os avisos de conversão sempre que tento adicionar dois valores, fazer uma operação bit a bit etc. O aviso tem algo como conversion to 'uint16_t' from 'int' may alter its value [-Wconversion]
. A questão é discutida aqui e aqui .
Isso não acontece ao usar variáveis declaradas como int
ou unsigned int
.
Para dar alguns exemplos, é necessário:
uint16_t value16;
uint8_t value8;
Eu teria que mudar isso:
value16 <<= 8;
value8 += 2;
para isso:
value16 = (uint16_t)(value16 << 8);
value8 = (uint8_t)(value8 + 2);
É feio, mas posso fazê-lo, se necessário. Aqui estão as minhas perguntas:
Existe um caso em que a conversão de não assinado para assinado e de volta para não assinado tornará o resultado incorreto?
Existem outros grandes motivos a favor / contra o uso dos tipos inteiros stdint.h?
Com base nas respostas que estou recebendo, parece que os tipos stdint.h geralmente são os preferidos, mesmo que c seja convertido uint
para int
e para trás. Isso leva a uma pergunta maior:
- Eu posso evitar os avisos do compilador usando typecasting (por exemplo
value16 = (uint16_t)(value16 << 8);
). Estou apenas escondendo o problema? Existe uma maneira melhor de fazer isso?
8u
e2u
.value8 += 2u;
evalue8 = value8 + 2u;
, mas recebo os mesmos avisos.Respostas:
Um compilador que esteja em conformidade com padrões,
int
entre 17 e 32 bits, pode legitimamente fazer o que quiser com o seguinte código:Uma implementação que quisesse fazer isso poderia legitimamente gerar um programa que não faria nada, exceto gerar a sequência "Fred" repetidamente em cada pino de porta usando todos os protocolos imagináveis. A probabilidade de um programa ser portado para uma implementação que faria tal coisa é excepcionalmente baixa, mas é teoricamente possível. Se quiser escrever o código acima para garantir que não se envolva em comportamento indefinido, seria necessário escrever a última expressão como
(uint32_t)x*x
ou1u*x*x
. Em um compiladorint
entre 17 e 31 bits, a última expressão cortaria os bits superiores, mas não se envolveria em comportamento indefinido.Acho que os avisos do gcc provavelmente estão tentando sugerir que o código escrito não é totalmente 100% portátil. Há momentos em que o código realmente deve ser escrito para evitar comportamentos que seriam indefinidos em algumas implementações, mas em muitos outros casos deve-se simplesmente imaginar que é improvável que o código seja usado em implementações que fariam coisas muito irritantes.
Observe que o uso de tipos como
int
eshort
pode eliminar alguns avisos e corrigir alguns problemas, mas provavelmente criaria outros. A interação entre tipos comouint16_t
e as regras de promoção inteira de C é nojenta, mas esses tipos ainda são provavelmente melhores do que qualquer alternativa.fonte
1) Se você converter de um inteiro não assinado para um assinado do mesmo comprimento, sem nenhuma operação intermediária, obterá o mesmo resultado todas as vezes, portanto não há problema aqui. Mas várias operações lógicas e aritméticas estão agindo de maneira diferente em operandos assinados e não assinados.
2) A principal razão para usar
stdint.h
tipos é que o tamanho de tais tipos de bits são definidas e igual em todas as plataformas, o que não é verdade paraint
,long
etc, bem comochar
não tem signess padrão, que pode ser com ou sem sinal de padrão. Torna mais fácil manipular os dados sabendo o tamanho exato sem usar verificações e suposições extras.fonte
int32_t
euint32_t
são iguais em todas as plataformas em que estão definidos . Se o processador não tiver um tipo de hardware exatamente igual , esses tipos não serão definidos. Daí a vantagem deint
etc. e, talvez,int_least32_t
etc.Como o número 2 de Eugene é provavelmente o ponto mais importante, eu gostaria de acrescentar que é um aviso
Também Jack Ganssle parece ser um defensor dessa regra: http://www.ganssle.com/tem/tem265.html
fonte
uint32_t
.Uma maneira fácil de eliminar os avisos é evitar o uso de -Wconversion no GCC. Eu acho que você precisa habilitar essa opção manualmente, mas se não, você pode usar -Wno-conversion para desativá-la. Você pode ativar avisos para conversões de precisão de sinal e FP através de outras opções , se ainda assim desejar.
Os avisos -Wconversion são quase sempre falsos positivos, e é provavelmente por isso que nem o -Wextra o habilita por padrão. Uma pergunta de estouro de pilha tem muitas sugestões para bons conjuntos de opções. Com base na minha própria experiência, este é um bom lugar para começar:
Adicione mais se precisar, mas é provável que não.
Se você precisar manter -Wconversion, poderá reduzir um pouco o seu código digitando apenas o operando numérico:
Isso não é fácil de ler sem o destaque da sintaxe.
fonte
em qualquer projeto de software, é muito importante usar definições de tipo portáteis. (até a próxima versão do mesmo compilador precisa dessa consideração.) Um bom exemplo, há vários anos, trabalhei em um projeto em que o compilador atual definia 'int' como 8 bits. A próxima versão do compilador definiu 'int' como 16 bits. Como não usamos definições portáteis para 'int', o ram (efetivamente) dobrou de tamanho e muitas sequências de código que dependiam de um int de 8 bits falharam. O uso de uma definição de tipo portátil teria evitado esse problema (centenas de horas-homem para corrigir).
fonte
int
para se referir a um tipo de 8 bits. Mesmo que um compilador não-C como o CCS faça isso, um código razoável deve usarchar
um tipo ou tipo de digitação por 8 bits e um tipo de digitação por tipo (não "longo") por 16 bits. Por outro lado, a transferência de código de algo como o CCS para um compilador real pode ser problemática, mesmo que use typedefs apropriados, pois esses compiladores geralmente são "incomuns" de outras maneiras.Sim. Um número inteiro assinado de n bits pode representar aproximadamente metade do número de números não negativos como um número inteiro não assinado de n bits, e confiar nas características de estouro é um comportamento indefinido, para que tudo possa acontecer. A grande maioria dos processadores atuais e anteriores usa dois complementos, de modo que muitas operações fazem a mesma coisa em tipos integrais assinados e não assinados, mas mesmo assim nem todas as operações produzirão resultados idênticos em termos de bits. Você está realmente pedindo problemas extras mais tarde, quando não consegue entender por que seu código não está funcionando conforme o esperado.
Embora int e unsigned tenham tamanhos de implementação definidos, eles geralmente são escolhidos "de maneira inteligente" pela implementação, por motivos de tamanho ou velocidade. Eu geralmente os mantenho, a menos que tenha uma boa razão para fazer o contrário. Da mesma forma, ao considerar se devo usar int ou não assinado, geralmente prefiro int, a menos que tenha um bom motivo para fazê-lo de outra forma.
Nos casos em que eu realmente preciso de um melhor controle sobre o tamanho ou a assinatura de um tipo, geralmente preferirei usar um typedef definido pelo sistema (size_t, intmax_t etc.) ou criar meu próprio typedef que indica a função de um determinado tipo (prng_int, adc_int, etc.).
fonte
Geralmente, o código é usado no ARM thumb e AVR (e x86, powerPC e outras arquiteturas), e 16 ou 32 bits podem ser mais eficientes (nos dois sentidos: flash e ciclos) no STM32 ARM, mesmo para uma variável que se encaixa em 8 bits ( 8 bits é mais eficiente no AVR) . No entanto, se a SRAM estiver quase cheia, a reversão para 8 bits para vars globais pode ser razoável (mas não para vars locais). Para portabilidade e manutenção (especialmente para vars de 8 bits), há vantagens (sem nenhuma desvantagem) em especificar o tamanho MÍNIMO adequado , em vez do tamanho exato e do typedef em um local .h (geralmente em ifdef) para ajustar (possivelmente uint_fast8_t / uint_least8_t) durante o tempo de transferência / compilação, por exemplo:
A biblioteca GNU ajuda um pouco, mas normalmente os typedefs fazem sentido de qualquer maneira:
// mas uint_fast8_t para AMBOS quando SRAM não é uma preocupação.
fonte