Warum muss ein Short vor arithmetischen Operationen in C und C ++ in ein int konvertiert werden?

74

Aus den Antworten, die ich aus dieser Frage erhalten habe , geht hervor, dass C ++ diese Anforderung für die Konvertierung shortin intbei der Ausführung von arithmetischen Operationen von C geerbt hat . Darf ich Ihnen überlegen, warum dies überhaupt in C eingeführt wurde? Warum nicht einfach diese Operationen als short?

Zum Beispiel ( entnommen aus dem Vorschlag von dyp in den Kommentaren ):

short s = 1, t = 2 ;
auto  x = s + t ;

xwird Typ von int haben .

Dayuloli
quelle
7
@ Jefffrey Integral Promotion ist Teil der üblichen arithmetischen Konvertierungen. short s=1, t=2; auto x = s+t;dann xist ein int.
Dyp
3
maxshort + maxshort> maxshort
Technosaurus
23
@technosaurus, der nicht erklären würde, warum intnicht befördert wird long(maxint + maxint> maxint).
Schuh
10
Ich bekomme keine Gegenstimmen zu dieser Frage. Dies ist eine gute Frage mit einer interessanten Antwort. Vier Abstimmungen und keine Kommentare sind ziemlich entmutigend.
Shafik Yaghmour
1
@dyp: Die Regeln für das, warum xist der Typ intsind völlig verschieden in C und C ++ , obwohl ... ;-)
Deduplicator

Antworten:

42

Wenn wir uns die Gründe für den internationalen Standard - Programmiersprachen - C im Abschnitt 6.3.1.8 Übliche arithmetische Konvertierungen ansehen , heißt es ( Hervorhebung meiner in Zukunft ):

Die Regeln im Standard für diese Konvertierungen sind geringfügige Änderungen gegenüber denen in K & R: Die Änderungen berücksichtigen die hinzugefügten Typen und die werterhaltenden Regeln. Es wurde eine explizite Lizenz hinzugefügt, um Berechnungen in einem „breiteren“ Typ als unbedingt erforderlich durchzuführen, da dies manchmal zu kleinerem und schnellerem Code führen kann, ganz zu schweigen von der richtigen Antwort häufiger . Berechnungen können auch in einem "engeren" Typ nach der Als-ob-Regel durchgeführt werden, solange das gleiche Endergebnis erzielt wird. Explizites Casting kann immer verwendet werden, um einen Wert in einem gewünschten Typ zu erhalten

Abschnitt 6.3.1.8 des Entwurfs der C99-Norm behandelt die üblichen arithmetischen Konvertierungen, die auf Operanden arithmetischer Ausdrücke angewendet werden, z. B. Abschnitt 6.5.6 Additive Operatoren lautet:

Wenn beide Operanden vom arithmetischen Typ sind, werden die üblichen arithmetischen Konvertierungen für sie ausgeführt.

Einen ähnlichen Text finden wir auch in Abschnitt 6.5.5 Multiplikative Operatoren . Im Fall eines kurzen Operanden werden zuerst die Ganzzahl- Heraufstufungen aus Abschnitt 6.3.1.1 Boolescher Wert, Zeichen und Ganzzahlen angewendet , der besagt:

Wenn ein int alle Werte des ursprünglichen Typs darstellen kann, wird der Wert in einen int konvertiert. Andernfalls wird es in ein vorzeichenloses int konvertiert. Diese werden als Integer-Promotions bezeichnet . 48) Alle anderen Typen bleiben durch die ganzzahligen Aktionen unverändert.

Die Diskussion aus dem Abschnitt 6.3.1.1der Begründung oder des internationalen Standards - Programmiersprachen - C über ganzzahlige Beförderungen ist tatsächlich interessanter. Ich werde b / c selektiv zitieren. Es ist zu lang, um sie vollständig zu zitieren:

Die Umsetzung fiel in zwei große Lager, die als nicht signierte Erhaltung und Werterhaltung charakterisiert werden können .

[...]

Der Ansatz der vorzeichenlosen Aufbewahrung erfordert die Heraufstufung der beiden kleineren vorzeichenlosen Typen auf vorzeichenlose int. Dies ist eine einfache Regel und ergibt einen Typ, der von der Ausführungsumgebung unabhängig ist.

Der werterhaltende Ansatz erfordert das Heraufstufen dieser Typen auf signiertes int, wenn dieser Typ alle Werte des ursprünglichen Typs ordnungsgemäß darstellen kann, und ansonsten das Heraufstufen dieser Typen auf vorzeichenloses int. Wenn also die Ausführungsumgebung short als etwas kleineres als int darstellt, wird unsigned short zu int; Andernfalls wird int ohne Vorzeichen.

Dies kann in einigen Fällen zu unerwarteten Ergebnissen führen, wie das inkonsistente Verhalten der impliziten Konvertierung zwischen vorzeichenlosen und größeren vorzeichenbehafteten Typen zeigt. Es gibt noch viele weitere Beispiele dafür. Obwohl dies in den meisten Fällen dazu führt, dass die Vorgänge wie erwartet funktionieren.

Shafik Yaghmour
quelle
2
Ja, manchmal wird es kleiner und schneller, weil Sie keine zusätzlichen Anweisungen zum Signieren / Nullen benötigen, um die Werte auf int zu erweitern oder die hohen Bits zu maskieren. In x86 benötigen Sie auch keine zusätzlichen Anweisungspräfixe, um die Argumentgröße zu ändern
phuclv
Schade, dass die Begründung keine sekundäre Regel hinzugefügt hat: Wenn das Ergebnis eines additiven, multiplikativen oder bitweisen Operators zu einem vorzeichenlosen Typ gezwungen wird, der kleiner als ist int, verhält sich der Ausdruck so, als ob seine Operanden ebenfalls erzwungen und die Operation für den ausgeführt würden kleinerer Typ. Es gibt keine definierten Fälle, die einer solchen Regel widersprechen würden, aber einige Compiler verwenden möglicherweise die Werbung als Ausrede, um daraus zu schließen, dass eine Aussage wie like x*=y;(mit beiden Variablen unsigned short) verspricht, x2147483648 / y nicht zu überschreiten.
Supercat
wenn ich so etwas habe int x = 1234und char *y = &x. Binäre Darstellung von 1234 ist 00000000 00000000 00000100 11010010. Meine Maschine ist Little Endian, also kehrt sie es um und das Speichern im Speicher 11010010 00000100 00000000 00000000LSB steht an erster Stelle. Jetzt Hauptteil. wenn ich benutze printf("%d" , *p). printflesen werden erste Byte ist 11010010der Ausgang nur , -46sondern 11010010ist 210so , warum gedruckt wird es -46. Ich bin wirklich verwirrt, ich denke, ein Zeichen für eine ganzzahlige Werbung macht etwas, aber ich weiß es nicht.
Suraj Jain
Sie zitieren den C99-Standard, aber ist dieses Verhalten nicht älter als das? Ich muss ins Bett, o / w würde ich sehen, ob ich etwas in K & R finden könnte.
PJTraill
@PJTraill gut Wikipedia zeigt auf eine Version von c89, obwohl Sie keinen offiziellen Entwurf bekommen können. In dieser Version unter Übliche arithmetische Konvertierungen wird ein sehr ähnliches Verfahren beschrieben. Also würde ich ja sagen. Beachten Sie, dass das obige Zitat geringfügige Änderungen an denen in K & R enthält, sodass K & R anders sein sollte.
Shafik Yaghmour
22

Dies ist weniger ein Merkmal der Sprache als vielmehr eine Einschränkung der physischen Prozessorarchitekturen, auf denen der Code ausgeführt wird. Der intTyper in C entspricht normalerweise der Größe Ihres Standard-CPU-Registers. Mehr Silizium nimmt mehr Platz und mehr Leistung in Anspruch, sodass in vielen Fällen nur mit den Datentypen "natürliche Größe" gerechnet werden kann. Dies ist nicht allgemein gültig, aber die meisten Architekturen haben immer noch diese Einschränkung. Mit anderen Worten, wenn zwei 8-Bit-Zahlen hinzugefügt werden, geschieht im Prozessor tatsächlich eine Art 32-Bit-Arithmetik, gefolgt von einer einfachen Bitmaske oder einer anderen geeigneten Typkonvertierung.

Phonon
quelle
4
Ich bin mir nicht sicher, ob es unbedingt eine kleine Maske gibt. Der Prozessor führt die Arithmetik in seiner nativen Wortgröße durch und speichert dann nur die unteren Bits zurück im Speicher. (Auch wenn Sie Recht haben, dass die meisten Architekturen nur Wortarithmetik betreiben, ist die einzige bemerkenswerte Ausnahme, Intel, ziemlich weit verbreitet.)
James Kanze
@ JamesKanze Du hast recht. Ich habe per Antwort bearbeitet. Und ja, Intel ist weit draußen, wenn es um optimierte Arithmetik geht, insbesondere mit seinen IPP-Bibliotheken.
Phonon
10
Ich bin nicht einverstanden mit "es ist kein Merkmal der Sprache"; es ist ein Merkmal der Sprache. Es ist so definiert, weil ... aber es wird durch die Sprache definiert, nicht durch den Prozessor.
Jonathan Leffler
2
@ JonathanLeffler Es ist sicherlich ein Merkmal der Sprache. Von den meisten Sprachen denke ich. Die Antwort von Phonon erklärt jedoch, warum Sprachen diese Funktion haben. (Es ist wahrscheinlich erwähnenswert, dass Maschinen in der Vergangenheit nur Wörter hatten, keine Bytes, Halbwörter usw. Und als die Byteadressierung eingeführt wurde, wirkte sich dies nur auf den Speicherzugriff aus, nicht auf Register und Operationen. Also, während der PDP-11 dies tat Sowohl Byte- als auch Wortbefehle, wenn die Zieladresse eines Bytebefehls ein Register war, wurde das Byte vorzeichenweise auf ein Wort erweitert.)
James Kanze
2
Wie die CPU Befehle ausführt, ist dem Benutzercode vollständig verborgen. Sie haben die Frage überhaupt nicht beantwortet.
Sophit
18

shortund charTypen werden von der Standardart "Speichertypen" berücksichtigt, dh Unterbereiche, mit denen Sie Platz sparen können, die Ihnen jedoch keine Geschwindigkeit verschaffen, da ihre Größe für die CPU "unnatürlich" ist.

Auf bestimmten CPUs ist dies nicht der Fall, aber gute Compiler sind klug genug zu bemerken, dass Sie die unsigned char -> intKonvertierung nicht durchführen müssen, wenn Sie beispielsweise einem vorzeichenlosen Zeichen eine Konstante hinzufügen und das Ergebnis in einem vorzeichenlosen Zeichen speichern . Zum Beispiel mit g ++ der Code, der für die innere Schleife von generiert wurde

void incbuf(unsigned char *buf, int size) {
    for (int i=0; i<size; i++) {
        buf[i] = buf[i] + 1;
    }
}

ist nur

.L3:
    addb    $1, (%rdi,%rax)
    addq    $1, %rax
    cmpl    %eax, %esi
    jg  .L3
.L1:

Hier sehen Sie, dass eine vorzeichenlose Anweisung zum Hinzufügen von Zeichen ( addb) verwendet wird.

Das gleiche passiert, wenn Sie Ihre Berechnungen zwischen kurzen Ints durchführen und das Ergebnis in kurzen Ints speichern.

6502
quelle
8

Die verknüpfte Frage scheint es ziemlich gut abzudecken: Die CPU tut es einfach nicht. Bei einer 32-Bit-CPU sind die nativen arithmetischen Operationen für 32-Bit-Register eingerichtet. Der Prozessor arbeitet lieber in seiner bevorzugten Größe, und für Operationen wie diese ist das Kopieren eines kleinen Werts in ein Register mit nativer Größe billig. (Für die x86-Architektur werden die 32-Bit-Register so benannt, als wären sie erweiterte Versionen der 16-Bit-Register ( eaxto ax, ebxto bxusw.); siehe x86-Integer-Anweisungen ).

Für einige extrem gebräuchliche Operationen, insbesondere Vektor / Float-Arithmetik, kann es spezielle Anweisungen geben, die mit einem anderen Registertyp oder einer anderen Registegröße arbeiten. Für so etwas wie eine kurze Zeit hat das Auffüllen mit (bis zu) 16 Bit Nullen nur sehr geringe Leistungskosten, und das Hinzufügen spezieller Anweisungen ist wahrscheinlich weder Zeit noch Platz auf dem Würfel wert (wenn Sie wirklich physisch wissen möchten, warum; ich bin es Ich bin mir nicht sicher, ob sie tatsächlich Platz beanspruchen würden, aber es wird viel komplexer.

ssube
quelle
2
Dies ist kein reines Hardwareproblem. Bei der Ausarbeitung des C99-Standards wurde bewusst entschieden, dass ganzzahlige Werbeaktionen auf eine bestimmte Weise funktionieren.
Shafik Yaghmour
4
"Beachten Sie, dass die 32-Bit-Register auch so benannt werden, als wären sie erweiterte Versionen der 16-Bit-Register (eax zu ax, ebx zu bx usw.)" Dies gilt für x86, ist jedoch für die meisten anderen Architekturen nicht korrekt . MIPS-Register haben unabhängig vom 32- oder 64-Bit-Modus den gleichen Namen und arbeiten immer in der nativen Größe, sodass Sie ohnehin keine Arithmetik in 8 oder 16 Bit durchführen können
phuclv