2015-01-13 9 views
7

Ich muss 2 vorzeichenlose 8-Bit-Werte nehmen und sie subtrahieren, dann diesen Wert zu einem 32-Bit-Akkumulator hinzufügen. Die 8-Bit-Subtraktion kann unterlaufen, und das ist in Ordnung (vorzeichenloser int-Unterlauf ist definiertes Verhalten, also keine Probleme dort).Warum ist static_cast auf einen Ausdruck verteilt?

Ich würde erwarten, dass static_cast<uint32_t>(foo - bar) tun sollte, was ich will (wo foo und bar sind beide uint8_t). Aber es scheint, dass dies sie zuerst wirft und dann führt eine 32-Bit-Subtraktion, während ich es als 8-Bit-Variable unterlaufen muss. Ich weiß, ich könnte nur mod 256, aber ich versuche herauszufinden, warum es funktioniert so.

Beispiel hier: https://ideone.com/TwOmTO

uint8_t foo = 5; 
uint8_t bar = 250; 

uint8_t diff8bit = foo - bar; 
uint32_t diff1 = static_cast<uint32_t>(diff8bit); 

uint32_t diff2 = static_cast<uint32_t>(foo) - static_cast<uint32_t>(bar); 

uint32_t diff3 = static_cast<uint32_t>(foo - bar); 

printf("diff1 = %u\n", diff1); 
printf("diff2 = %u\n", diff2); 
printf("diff3 = %u\n", diff3); 

Ausgang:

diff1 = 11 
diff2 = 4294967051 
diff3 = 4294967051 

Ich würde vermuten diff3 das gleiche Verhalten wie diff1 haben würde, aber es ist eigentlich die gleiche wie diff2.

Warum passiert das? Soweit ich das beurteilen kann, sollte der Compiler die beiden 8-Bit-Werte subtrahieren und dann auf 32-Bit umwandeln, aber das ist eindeutig nicht der Fall. Hat das etwas mit der Spezifikation zu tun, wie sich static_cast auf einem Ausdruck verhält?

+3

Dies ist ein Beispiel von * integraler Förderung * über die * üblichen arithmetischen Umwandlungen *. Es wird wegen des Subtraktionsausdrucks angewendet, nicht des 'static_cast'. – dyp

+0

Warum würden Sie denken, dass diff2 dasselbe wie diff1 wäre? diff2 führt die Subtraktion mit zwei 'uint32_t's eindeutig aus. Was das Ergebnis von diff3 betrifft - der Compiler hat entschieden, dass es das Richtige ist (was es ist, es wird Ihnen die korrekteste Antwort geben). Wenn du willst, dass es etwas anderes macht, musst du es ihm sagen. – mbgda

+0

@mbgda Sie erwarten, dass diff3 und diff1 gleich sind. –

Antwort

8

Für die meisten arithmetischen Operatoren (einschließlich -) durchlaufen die Operanden die üblichen arithmetischen Umwandlungen. Eine dieser Umsetzungen besteht darin, dass jeder Wert des Typs, der schmaler als int ist, zu int heraufgestuft wird. (Standardreferenz: [expr]/10).

So wird der Ausdruck foo - bar wird (int)foo - (int)bar geben (int)-245. Dann werfen Sie das auf uint32_t, was eine große positive Zahl ergibt.

Um das Ergebnis zu erhalten, das Sie beabsichtigen, können Sie auf uint8_t statt uint32_t umwandeln. Alternativ verwenden Sie den Modulo-Operator % über das Ergebnis der Besetzung zu uint32_t.

Es ist nicht möglich, eine Berechnung in enger Präzision zu tun, als int

+1

Interessant, danke! Wenn dies also auf einer 8-Bit-CPU gemacht worden wäre, hätte es das Ergebnis geliefert, das ich ursprünglich erwartet hatte? –

+1

@KeytarHero der C++ - Standard gibt an, dass "int" mindestens 16-Bit ist (auf einer 8-Bit-CPU muss der Compiler dann einige Registerpaare oder Ähnliches verwenden, um die Standardanforderungen zu erfüllen) –

4

Das Problem ist nicht die static_cast aber die Subtraktion, die Operanden von additiven Operatoren haben die üblichen arithmetischen Umwandlungen auf sie angewandt und in diesem Fall die integrierten Aktionen, die in den beiden Operanden der Subtraktion int gefördert Ergebnisse:

static_cast<uint32_t>(foo - bar); 
         ^^^ ^^^ 

Auf der anderen Seite:

static_cast<uint8_t>(foo - bar); 

würde gewünschtes Ergebnis.

aus dem Entwurf C++ Standard Abschnitt 5.7[expr.add] sagt:

Die additiven Operatoren + und - Gruppe von links nach rechts. Die üblichen arithmetischen Umrechnungen werden für Operanden des arithmetischen Typs oder des Aufzählungstyps durchgeführt.

dies führt zu den integralen Aktionen, Abschnitt 5[ausdr] sagt:

Ansonsten sind die Integral-Promotions (4.5) ist für beide Operanden durchgeführt werden

die Ergebnisse In beiden Operanden wird umgewandelt in int, Abschnitt 4.5[conv.prom] sagt:

A prvalue einer ganzen Zahl anderen Typ als bool, char16_t, char32_t oder Wchar_t deren ganzzahlige Umwandlung Rang (4,13) geringer ist als der Rang des int kann auf einen prvalue vom Typ umgewandelt werden, int if int kann alle die Werte des Quelltyps darstellen; Andernfalls kann der Quellprwert in einen Pr-Wert vom Typ unsigned int konvertiert werden.

und dann die static_cast zu uint32_t aufgebracht wird, die in einem Konvertierungsergebnisse, die in Abschnitt wie folgt definiert ist 4.7[conv.integral]:

Wenn der Zieltyp nicht signiert ist, der resultierende Wert ist die am wenigsten vorzeichenlose Ganzzahl, die kongruent zur Quelle Integer ist (Modulo 2n, wobei n die Anzahl der Bits ist, die zur Darstellung des Typs ohne Vorzeichen verwendet werden). [

Die Fragen Why must a short be converted to an int before arithmetic operations in C and C++? erklärt, warum Typen kleiner als int für arithmetische Operationen gefördert werden.