
Julien REINAULD
Estoy bastante confundido por el siguiente código:
#include <stdio.h>
#include <stdint.h>
int main(int argc, char ** argv)
{
uint16_t a = 413;
uint16_t b = 64948;
fprintf(stdout, "%u\n", (a - b));
fprintf(stdout, "%u\n", ((uint16_t) (a - b)));
return 0;
}
Eso devuelve:
$ gcc -Wall test.c -o test
$ ./test
4294902761
1001
$
Parece que la expresión (a – b) tiene tipo uint32_t. No entiendo por qué ya que ambos operadores son uint16_t.
¿Puede alguien explicarme esto?
El estándar C explica esto con bastante claridad (§6.5.6 Operadores aditivos):
Si ambos operandos tienen tipo aritmético, las conversiones aritméticas habituales se realizan sobre ellos.
(§6.3.1.8 Conversiones aritméticas habituales):
… los promociones enteras se realizan en ambos operandos.
(§6.3.1.1 Booleanos, caracteres y números enteros):
Si una int
puede representar todos los valores del tipo original, el valor se convierte en un int
; … Estos se llaman las promociones enteras. Todos los demás tipos no se modifican por las promociones de enteros.
Ya que int
puede representar todos los valores de uint16_t
en tu plataforma, a
y b
se convierten en int
antes de realizar la resta. El resultado tiene tipo int
y se pasa a printf
como un int
. Ha especificado el %u
formateador con un int
argumento; estrictamente hablando, esto invoca un comportamiento indefinido, pero en su plataforma el int
argumento se interpreta como su representación de complemento a dos, y eso se imprime.
Si descarta los bits superiores de un número (mediante la conversión explícita a un entero sin signo de 16 bits), obtendrá un resultado más pequeño (dentro del rango de 0 y 2^16-1) que antes de.

relajarse
C promueve los argumentos para unsigned int
antes de hacer la resta. Este es un comportamiento estándar.
Véase, por ejemplo, en una expresión de C donde están presentes un int sin signo y un int con signo, ¿qué tipo se promoverá a qué tipo? para detalles.
Estás declarando ambos como enteros sin signo y luego haciendo una resta que dará como resultado un número negativo. No puede esperar un comportamiento confiable cuando está haciendo un mal uso de los tipos de datos.
– en eso
31 de octubre de 2011 a las 14:04
En realidad, eso es exactamente lo que necesito hacer.
–Julián REINAULD
31 de octubre de 2011 a las 14:11
Así que estaba pensando: en realidad, eso es exactamente lo que necesito hacer. a y b representan los valores de un contador de 16 bits en una pieza de hardware. Un contador de 16 bits cuenta de 0 a 65535. Entonces, a y b son uint16_t. Ahora si quiero saber la cantidad de ciclos que pasaron entre 2 marcas de tiempo ayb, solo tengo que hacer la resta. Si el contador pasó de 10 a 100, sé que han pasado 90 ciclos. Si el contador quiere pasar de 65530 a 2, es decir, pasó de 65530 a 65535, luego volvió a 0 y pasó a 2, sé que pasaron 5 + 1 + 2 = 8 ciclos.
–Julián REINAULD
31 de octubre de 2011 a las 14:22
Honestamente, esto suena como un problema de diseño. Primero, si es posible que pasen más de 65535 ciclos entre un cheque, su diseño es inherentemente defectuoso ya que ni siquiera puede representar ese número con su tipo de datos. Además, necesita saber de alguna manera si el contador ha retrocedido o no de 65535 a 0 (a menos que asuma que si es más pequeño, se ha retrocedido, pero nuevamente está roto si tiene más de 65535 ciclos entre controles). Si puede garantizar que pasen menos de 65535 ciclos entre comprobaciones, simplemente hágalo si b > a ? b – a : (((65335 – a) + b) + 1). Podría ser capaz de optimizar eso también.
– en eso
31 de octubre de 2011 a las 14:34
b – a es mucho más simple que b > a ? b – a : (((65335 – a) + b) + 1) …
–Julián REINAULD
31 de octubre de 2011 a las 14:42