Aloha :)
Es gibt zwei wichtige Grundprinzipien in "C":
1) Programmierer wissen was sie tun \(\implies\) der Compiler hinterfragt keinen Code
2) Du bezahlst nur das, was du brauchst \(\implies\) der Compiler optimiert alles Unnötige weg
Gerade im Embedded-Bereich, wo man oft nur wenig Speicher hat und auf einer speziellen Hardware "tricksen" muss, sind diese Prinzipien nützlich. Das macht das Programmieren in C aber auch schwierig, weil man wirklich verstehen muss, was im Hintergrund passiert.
Das Entscheidende passiert in der Zeile
x= a*b;
In "C" ist der kleinst-mögliche Datentyp für Ganzzahl-Berechungen der Typ int. Das heißt, die beiden unsigned short Variablen a und b werden vor der Multiplikation in int umgewandelt. Der Compiler interpretiert die Programmzeile also so:
x= (int)a * (int)b;
Bei der Multiplikation der beiden int-Werte kommt es nun mit deinen Übergabeparametern (65535) zu einem Integer-Überlauf. Der führt bereits nach dem C-Standard zu undefiniertem Verhalten (UB = Undefined Behaviour). Wegen Regel (1) ignoriert der Compiler das aber, es könnte ja auf der speziellen Hardware vom Programmierer genau so gewollt sein.
Das Ergebnis der int-Multiplikation ist wieder vom Typ int, sodass der Compiler bei der Zuweisung an \(x\) eine automatische Konvertierung nach unsigned int durchführt. Hier sollte es zumindest auf höchster Warnstufe einen Hinweis vom Compiler geben.
In der nächsten Zeile
if (x>=0x80000000)
kommt nun die Optimierung ins Spiel. Da \(x\) zuvor ein int-Wert zugewiesen wurde, kann die Bedingung niemals erfüllt sein, denn der Maximalwert für int ist ja 0x7FFFFFFF. Der if-Zweig kann also aus Sicht des Compilers nie durchlaufen werden, sodass dieser nach Regel (2) vom Compiler wegoptimiert wird. Im Endeffekt wird daher nur der Code im else-Zweig compiliert.
Zusammengefasst wir das Folgende compiliert:
void myfunc( unsigned short a, unsigned short b) {
unsigned int x;
x= (int)a * (int)b;
printf("%u < %u\n", x, 0x80000000);
}
Das Ergebnis ist das, was du beschrieben hast...
Schau dir auch mal selbst an, was der C-Compiler aus deinem Code macht:
https://godbolt.org/
Du wirst überrascht sein, wie unglaublich effizient C übersetzt wird.
PS: Vor dem C99-Standard wurden auch sämtliche Fließkomma-Operationen mindestens im Datentyp double durchgeführt. Das heißt, alle float-Werte wurden vor irgendeiner Rechnung stets nach double konvertiert. Seit dem C99-Standard muss der Compiler diese Hochskalierung nicht mehr machen. Mit dem vordefinierten Macro FLT_EVAL_METHOD kannst du abfragen, welcher Datentyp als Minimum für die Fließkommaberechnungen verwendet wird: -1 unbekannt, 0 float, 1 double, 2 long double. Bei guten Compilern kann man das wählen.
Für ganzzahlige Rechnungen ist hingegen immer int als minimaler Datentyp gesetzt.