Uma característica interessante de C em comparação com outros idiomas é que muitos de seus tipos de dados são baseados no tamanho da palavra da arquitetura de destino, em vez de serem especificados em termos absolutos. Embora isso permita que o idioma seja usado para escrever código em máquinas que podem ter dificuldade com certos tipos, torna muito difícil projetar código que será executado de maneira consistente em diferentes arquiteturas. Considere o código:
uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;
Em uma arquitetura em que int
há 16 bits (ainda é verdade para muitos microcontroladores pequenos), esse código atribui o valor 1 usando um comportamento bem definido. Nas máquinas com int
64 bits, atribuiria um valor 4294836225, novamente usando um comportamento bem definido. Em máquinas com int
32 bits, provavelmente atribuiria um valor -131071 (não sei se isso seria Comportamento definido pela implementação ou indefinido). Mesmo que o código não use nada, exceto os que são nominalmente do tipo "tamanho fixo", o padrão exigiria que dois tipos diferentes de compilador em uso hoje produzissem dois resultados diferentes, e muitos compiladores populares hoje produzissem um terceiro.
Esse exemplo em particular é um tanto artificial, pois eu não esperaria que no código do mundo real atribuísse o produto de dois valores de 16 bits diretamente a um valor de 64 bits, mas foi escolhido como um breve exemplo para mostrar três maneiras inteiras as promoções podem interagir com tipos não assinados de tamanho supostamente fixo. Existem algumas situações do mundo real em que é necessário que a matemática em tipos não assinados seja executada de acordo com as regras da aritmética matemática inteira, outras em que é necessário que seja executada de acordo com as regras da aritmética modular e outras em que realmente não '' não importa. Muitos códigos do mundo real para coisas como somas de verificação baseiam-se no uint32_t
modelo de empacotamento aritmético 2³² e na capacidade de executar arbitrariamenteuint16_t
aritmética e obter resultados que são, no mínimo, definidos como sendo o mod preciso 65536 (em oposição a desencadear um comportamento indefinido).
Embora essa situação pareça claramente indesejável (e se torne ainda mais à medida que o processamento de 64 bits se torna a norma para muitos propósitos), o comitê de padrões C do que observei prefere introduzir recursos de linguagem que já são usados em algumas produções notáveis ambientes, em vez de inventá-los "do zero". Existem extensões notáveis para a linguagem C que permitam ao código especificar não apenas como um tipo será armazenado, mas também como ele deve se comportar em cenários que envolvam possíveis promoções? Eu posso ver pelo menos três maneiras pelas quais uma extensão do compilador pode resolver esses problemas:
Adicionando uma diretiva que instruiria o compilador a forçar certos tipos inteiros "fundamentais" a terem determinados tamanhos.
Adicionando uma diretiva que instruiria o compilador a avaliar vários cenários de promoção como se os tipos da máquina tivessem tamanhos específicos, independentemente dos tamanhos reais dos tipos na arquitetura de destino.
Ao permitir meios de declarar tipos com características específicas (por exemplo, declarar que um tipo deve se comportar como um anel algébrico mod-65536, independentemente do tamanho da palavra subjacente, e não deve ser implicitamente conversível em outros tipos; a adição de a
wrap32
aint
deve gerar um O resultado do tipo,wrap32
independentemente de terint
mais de 16 bits, ao adicionar umwrap32
diretamente a,wrap16
deve ser ilegal (já que nenhum deles pode ser convertido no outro).
Minha preferência seria a terceira alternativa, pois permitiria que mesmo máquinas com tamanhos incomuns de palavras trabalhem com muito código que espera que as variáveis sejam "quebradas", como faria com tamanhos de potência de dois; o compilador pode precisar adicionar instruções de mascaramento de bits para fazer com que o tipo se comporte adequadamente, mas se o código precisar de um tipo que envolva o mod 65536, é melhor que o compilador gere esse mascaramento em máquinas que precisam dele do que sobrecarregar o código-fonte com ele ou simplesmente ter esse código inutilizável em máquinas onde essa máscara seria necessária. No entanto, estou curioso para saber se existem extensões comuns que atingiriam o comportamento portátil por qualquer um dos meios acima, ou por alguns meios em que não pensei.
Para esclarecer o que estou procurando, há algumas coisas; mais notavelmente:
Embora existam muitas maneiras pelas quais o código pode ser escrito para garantir a semântica desejada (por exemplo, definir macros para executar cálculos em operandos não assinados de tamanho específico, de modo a produzir um resultado que explode ou não explique explicitamente) ou pelo menos evite indesejados semântica (por exemplo, defina condicionalmente um tipo
wrap32_t
parauint32_t
compiladores nos quais auint32_t
não seria promovida e calcule que é melhor o código que exigewrap32_t
falha na compilação nas máquinas onde esse tipo seria promovido do que executá-lo e gerar comportamento falso), se houver alguma maneira de escrever o código que seria mais favorável em futuras extensões de idioma, usá-lo seria melhor do que criar minha própria abordagem.Tenho algumas idéias bastante sólidas sobre como o idioma pode ser estendido para resolver muitos problemas de tamanho inteiro, permitindo que o código produza semânticas idênticas em máquinas com tamanhos de palavras diferentes, mas antes de gastar algum tempo significativo escrevendo-as, gostaria saber quais esforços nessa direção já foram empreendidos.
Eu não desejo de forma alguma ser menosprezado pelo Comitê de Padrões C ou pelo trabalho que eles produziram; Espero, no entanto, que dentro de alguns anos seja necessário fazer o código funcionar corretamente em máquinas onde o tipo de promoção "natural" teria 32 bits, bem como naqueles em que teria 64 bits. Eu acho que com algumas extensões modestas da linguagem (mais modestas do que muitas das outras alterações entre a C99 e a C14), seria possível não apenas fornecer uma maneira limpa de usar eficientemente as arquiteturas de 64 bits, mas também facilitar a interação com as máquinas de "tamanho incomum de palavras" que o padrão historicamente inclinou para trás para oferecer suporte [por exemplo, possibilitando que máquinas com um char
código de 12 bits executem um código que espera umuint32_t
para embrulhar mod 2³²]. Dependendo da direção que as extensões futuras tomarem, eu também esperaria que fosse possível definir macros que permitissem que o código escrito hoje seja utilizável nos compiladores de hoje em que os tipos inteiros padrão se comportam como "esperados", mas também nos futuros compiladores em que inteiros tipos seriam padrão se comportariam de maneira diferente, mas onde podem fornecer os comportamentos necessários.
fonte
int
for maior queuint16_t
, os operandos da multiplicação seriam promovidosint
e a multiplicação seria realizada comoint
multiplicação, e oint
valor resultante seria convertido emint64_t
para a inicialização dewho_knows
.int
, mas ainda foge em (Mais uma vez assumindo o meu entendimento do padrão C está correta.).Respostas:
Como a intenção típica de código como este
é realizar a multiplicação em 64 bits (o tamanho da variável em que o resultado é armazenado), a maneira usual de obter o resultado correto (independente da plataforma) é converter um dos operandos para forçar uma multiplicação de 64 bits:
Eu nunca encontrei nenhuma extensão C que torne esse processo automático.
fonte
uint32_t
ou usar uma macro definida como#define UMUL1616to16(x,y)((uint16_t)((uint16_t)(x)*(uint16_t)(y)))
ou#define UMUL1616to16(x,y)((uint16_t)((uint32_t)(x)*(uint16_t)(y)))
dependendo do tamanho deint
), mas sim se há quaisquer padrões emergentes de como lidar com essas coisas de maneira útil, em vez de definir minhas próprias macros.(ushort1*ushort2) & 65535u
seria executar a aritmética mod-65536 para todos os valores de operando. Lendo a lógica do C89, acho bastante claro que, embora os autores tenham reconhecido que esse código pode falhar em algumas implementações se o resultado exceder 2147483647, eles esperavam que essas implementações se tornassem cada vez mais raras. Esse código às vezes falha no gcc moderno, no entanto.