No, es un caso "raro" que ocurra esto, pero puede ocurrir, voy a mostrar un ejemplo y vamos a ver en ensamblador que es lo que hace el compilador, uso XC8 en ves de CCS pero es casi igual.. Esto suele ocurrir cuando uno maneja varios tipos de datos en una misma sentencia, ya que los numeros serian enteros sin signos de 16bits, y que luego no solo los pones en 32bits sino que ademas es con signo. Como casi siempre uno se maneja con numeros SIN signos ocurre poco. Vayamos al ejemplo, tal ves no tengas idea de ASM pero no importa, esta explicado que hace el codigo luego de darlo.
El codigo es este:
#include <xc.h>
#include <stdint.h>
#define UT (24180)
#define AC6 (15336)
#define AC5 (24741)
int32_t X1;
void main (void)
{
while(1)
{
// Codigo 1
X1 = ((int32_t)(UT-AC6) * AC5) >> 15;
// Codigo 2
X1 = ((UT-AC6) * AC5);
X1 >>= 15;
}
}
Codigo 1 en ASM:
0x1FF66: MOVLW 0x15
0x1FF68: MOVWF X1, ACCESS
0x1FF6A: MOVLW 0x1A
0x1FF6C: MOVWF 0x12, ACCESS
0x1FF6E: MOVLW 0x0
0x1FF70: MOVWF 0x13, ACCESS
0x1FF72: MOVLW 0x0
0x1FF74: MOVWF 0x14, ACCESS
Parece complicado, pero tenes que pensar que X1 es de 4 bytes, entonces como el micro utiliza registros de 1 byte, debe asignar 4 valores. Comienza asignado el valor 0x15 al byte de menor peso, luego 0x1A, luego 0x0 y finalmente 0x0. Es decir el primer "Codigo" al ser todos constantes el compilador precalcula y directamente reemplaza el valor por esa constante calculada ( No requiere instrucciones del micro para calcularlo).
Resumen asigna el valor 0x00001A15 (6677) a X1.
El codigo 2 consta de 2 partes, la primera es la asignacion a X1 y luego el shift
! Aca poniendo el valor
0x1FF76: MOVLW 0x3C
0x1FF78: MOVWF X1, ACCESS
0x1FF7A: MOVLW 0xC4
0x1FF7C: MOVWF 0x12, ACCESS
0x1FF7E: MOVLW 0xFF
0x1FF80: MOVWF 0x13, ACCESS
0x1FF82: MOVLW 0xFF
0x1FF84: MOVWF 0x14, ACCESS
! X1 >>= 15;
0x1FF86: MOVLW 0xF
0x1FF88: MOVWF 0xD, ACCESS
0x1FF8A: RLCF 0x14, W, ACCESS
0x1FF8C: RRCF 0x14, F, ACCESS
0x1FF8E: RRCF 0x13, F, ACCESS
0x1FF90: RRCF 0x12, F, ACCESS
0x1FF92: RRCF X1, F, ACCESS
0x1FF94: DECFSZ 0xD, F, ACCESS
0x1FF96: BRA 0xFF8A
Este codigo es interesante, en si la primer parte es una asignacion simple como ocurrio antes. Y aca es donde ocurre la "magia".
Supuestamente deberiamos tener el valor: (24180-15336) * 24741 = 218809404 = 0x0D0A.C43C (Separo cada 16 bits asi es mas entendible)
Pero mirando el codigo vemos que lo que se asigna en realidad es: 0xFFFF.C43C Lo cual es distinto A lo que deberia haber sido, seguramente hay algo importante que no estamos viendo.
Y lo que no estamos viendo es que C ejecuta las cosas "temporalmente" y luego asigna al lugar.
Aca viene la explicacion de porque ocurre esto.
Segundo caso ( el erroneo ) :Tengo numeros de 16 bits como lo son UT, AC6 y AC5. Entonces espero que las operaciones que se realizen con estos numeros enten dentro de los valores de esos 16 bits. En este caso lo precalcula el compilador, pero si hubieran sido registros de no hacerlo asi, deberia trabajar con todos numeros de 32bits para lo cual un microcontrolador de 8 bits le llevaria el doble de trabajo o mas en algunas operaciones. Asi que si tenes 2 enteros sin signo de 16bits y los restas, se van a generar instrucciones para que solamente sobrevivan 16bits. Me explico un poco mas:
Temporalmente trabaja con todos los bits. primero hace la resta, luego la multiplicacion y despues?. Al multiplicar le queda esto:
0x0D0A.C43C
Pero estoy trabajando con solo 16 bits entonces RECORTA esos 32 en solo 16bits quedando solamente: 0xC43C ( los ultimos 4 nibbles )
De alli ahora tiene eso y el shift que va a tener que ser un entero con signo.. Ese entero de 16bits se promociona a uno mas grande que es el entero con signo. Quedando 0xFFFF.C43C, por que queda asi? si recordas 0xC en binario es 1100 , el bit de mayor peso define el signo por lo tanto al pasarlo a un numero de mas bits se debe trasladar el signo por eso son todas las F (1111) al comienzo. Si rotamos van a ir agregandose unos a la izquierda para mantener el signo , por lo tanto llegariamos al punto de tener 0xFFFF.FFFF ( -1 )
Que es lo que me da el segundo codigo.
Ahora el primer codigo que es el que funciona:La diferencia radica en que le especificamos al compilador que el resultado de la resta lo guarde en un entero con signo de 32 bits. De esa forma internamente va a llevar los numeros ( UT y AC6) promocionarlos de 16bits a 32bits con signo y de alli operar, pero no va a recortarlo para la otra operacion ya que nosotros le dijimos que lo mantenga en 32bits. De alli simplemente es continuar y seguir haciendo lo normal.
Resumen, le cambiamos el comportamiento del compilador al obligarlo a que el resultado de la resta (que lo hizo con enteros sin signo o con signo) lo guarde y mantenga temporalmente en un entero con signo de 32bits. Y no proceda a recortarlo a esos 16 bits que eran el tipo de numero que tenia.