Ist der Operator + so in C implementiert?

79

Wenn das Verständnis , wie primitive Operatoren wie +, -, *und /sind in C implementiert, fand ich die folgenden Ausschnitt aus einer interessanten Antwort .

// replaces the + operator
int add(int x, int y) {
    while(x) {
        int t = (x & y) <<1;
        y ^= x;
        x = t;
    }
    return y;
}

Es scheint, dass diese Funktion zeigt, wie +tatsächlich im Hintergrund funktioniert . Es ist jedoch zu verwirrend für mich, um es zu verstehen. Ich habe geglaubt, dass solche Operationen mit Assembly-Direktiven ausgeführt werden, die vom Compiler für eine lange Zeit generiert wurden!

Wird der +Operator als der Code implementiert, der in den meisten Implementierungen angegeben ist? Nutzt dies das Komplement von zwei oder andere implementierungsabhängige Funktionen?

Nalzok
quelle
60
Ich denke, die meisten Implementierungen verwenden native addMaschinenanweisungen, die vermutlich alle CPUs haben und als Hardware-Addierer implementiert sind, die in wenigen Takten funktionieren.
MikeCAT
3
Ja, der +Bediener nutzt sehr wahrscheinlich implementierungsdefinierte Funktionen. Diese werden als "Maschinensprache" und "CPU" bezeichnet. Was ist deine Frage? Wenn Sie wissen möchten, wie Ausdrücke in Maschinencode konvertiert werden, lesen Sie bitte die Compilerkonstruktion.
zu ehrlich für diese Seite
Meinst du, der Code, den ich gepostet habe, ist völlig nutzlos, weil +s in Assembly-Direktiven wie addvom Compiler übersetzt werden?
Nalzok
11
Die meisten +Operationen werden in eine Variante (oder Kombination) von Maschinencode- addAnweisungen kompiliert . Ihr Code ist in jedem realen Szenario verworren und nutzlos, kann jedoch dazu dienen, binäre Operationen zu lehren.
Anders Tornblad
11
Es ist zwar nicht so, wie C es macht (siehe Antworten unten), aber es ist ziemlich nahe daran, wie die beteiligten Schaltkreise auf der niedrigsten Ebene addieren können. Versuchen Sie, es in Papier und Stift für kleine Binärwerte (z. B. 3- oder 4-Bit-Bytes) durchzuarbeiten, und sehen Sie, wie es funktioniert. Stellen Sie sich nun vor, wie Schaltkreise mit elektrischen Impulsen dasselbe tun könnten. Stellen Sie sich nun vor, Sie würden alle Bits parallel ausführen und nicht eine Schleife. Jetzt sind Sie bereit, einen Computer aus den 1940er Jahren zu bauen: D
Jon Hanna

Antworten:

184

Um pedantisch zu sein, gibt die C-Spezifikation nicht an, wie die Addition implementiert wird.

+Um jedoch realistisch zu sein, wird der Operator für ganzzahlige Typen, die kleiner oder gleich der Wortgröße Ihrer CPU sind, direkt in einen Additionsbefehl für die CPU übersetzt, und größere ganzzahlige Typen werden in mehrere Additionsbefehle mit einigen zusätzlichen Bits übersetzt, um den Überlauf zu behandeln.

Die CPU verwendet intern Logikschaltungen, um die Addition zu implementieren, und verwendet keine Schleifen, Bitverschiebungen oder andere Elemente, die der Funktionsweise von C sehr ähnlich sind.

orlp
quelle
12
Diese Antwort ist ausgezeichnet, weil sie mit ungewöhnlicher Klarheit und Einfachheit präsentiert wird. Ich finde es überhaupt nicht übermäßig pedantisch, nur die richtige Dosis Pedanterie für die Frage.
Jeremy Anderson
5
@orlp Tatsächlich können CPU-Logikschaltungen aus HDLs kompiliert werden, und Sie generieren wahrscheinlich einen Addierer mit Schleifen und Bitverschiebungen, die dem Vorschlag des OP vage ähnlich sind (aber nur vage). Diese Schleifen und Bitverschiebungen würden das Layout der Hardware und ihre Verbindung beschreiben. Andererseits könnte in erstklassiger Hardware jemand diese Schleifen und Bitshifts abrollen oder sogar die HDL beseitigen und die Schaltung manuell für etwas so leistungskritisches wie einen Addierer auslegen.
Yakk - Adam Nevraumont
5
Eine lineare Addiererschaltung macht genau das, was dieser C-Code macht, aber die Schleife wird in der Hardware vollständig abgewickelt (32 Mal).
usr
2
@usr nicht nur abgerollt, sondern jeder "Schritt" geschieht gleichzeitig.
OrangeDog
4
@OrangeDog Ein einfacher Hardware-Addierer hat einen Carry-Durchlauf, ähnlich wie dieser C-Code, der den Paralellismus einschränkt. Hochleistungsaddierer können Carry-Lookahead-Schaltungen verwenden, um dies zu reduzieren.
Plugwash
77

Wenn Sie zwei Bits hinzufügen, ist das folgende Ergebnis: (Wahrheitstabelle)

a | b | sum (a^b) | carry bit (a&b) (goes to next)
--+---+-----------+--------------------------------
0 | 0 |    0      | 0
0 | 1 |    1      | 0
1 | 0 |    1      | 0
1 | 1 |    0      | 1

Wenn Sie also bitweise xor ausführen, können Sie die Summe ohne Übertrag erhalten. Und wenn Sie bitweise tun und Sie können die Übertragsbits bekommen.

Erweiterung dieser Beobachtung für Multibit-Nummern aundb

a+b = sum_without_carry(a, b) + carry_bits(a, b) shifted by 1 bit left
    = a^b + ((a&b) << 1)

Einmal bist 0:

a+0 = a

Der Algorithmus läuft also auf Folgendes hinaus:

Add(a, b)
  if b == 0
    return a;
  else
    carry_bits = a & b;
    sum_bits = a ^ b;
    return Add(sum_bits, carry_bits << 1);

Wenn Sie die Rekursion loswerden und sie in eine Schleife konvertieren

Add(a, b)
  while(b != 0) {
    carry_bits = a & b;
    sum_bits = a ^ b;

    a = sum_bits;
    b = carrry_bits << 1;  // In next loop, add carry bits to a
  }
  return a;

In Anbetracht des obigen Algorithmus sollte die Erklärung aus dem Code einfacher sein:

int t = (x & y) << 1;

Tragen Sie Bits. Das Übertragsbit ist 1, wenn 1 Bit rechts in beiden Operanden 1 ist.

y ^= x;  // x is used now

Addition ohne Übertrag (Übertragsbits ignoriert)

x = t;

Verwenden Sie x erneut, um das Tragen festzulegen

while(x)

Wiederholen, solange mehr Übertragsbits vorhanden sind


Eine rekursive Implementierung (leichter zu verstehen) wäre:

int add(int x, int y) {
    return (y == 0) ? x : add(x ^ y, (x&y) << 1);
}

Diese Funktion zeigt anscheinend, wie + tatsächlich im Hintergrund funktioniert

Nein. Normalerweise (fast immer) bedeutet das Hinzufügen von Ganzzahlen das Hinzufügen von Maschinenbefehlen. Dies zeigt nur eine alternative Implementierung mit bitweisem xor und und.

Mohit Jain
quelle
5
Dies ist die beste Antwort imo, alle anderen Zustand , dass es in der Regel auf einen einzigen Befehl übersetzt wird , aber dies bedeutet , dass und auch erklärt die gegebene Funktion.
Nick Sweeting
@ NickSweeting Danke. Die Frage kann auf zwei Arten interpretiert werden, und ich denke, die akzeptierte Antwort hat sie richtig interpretiert, was das OP fragen wollte.
Mohit Jain
25

Diese Funktion zeigt anscheinend, wie + tatsächlich im Hintergrund funktioniert

Nein. Dies wird in die native addMaschinenanweisung übersetzt, die tatsächlich den Hardware-Addierer verwendet ALU.

Wenn Sie sich fragen, wie der Computer hinzufügt, finden Sie hier einen einfachen Addierer.

Alles im Computer wird mit Logikgattern ausgeführt, die hauptsächlich aus Transistoren bestehen. Der Volladdierer enthält Halbaddierer.

Ein grundlegendes Tutorial zu Logikgattern und Addierern finden Sie hier . Das Video ist sehr hilfreich, wenn auch lang.

In diesem Video wird ein einfacher Halbaddierer gezeigt. Wenn Sie eine kurze Beschreibung wünschen, ist dies:

Der Halbaddierer addiert zwei gegebene Bits. Die möglichen Kombinationen sind:

  • Addiere 0 und 0 = 0
  • Addiere 1 und 0 = 1
  • Addiere 1 und 1 = 10 (binär)

Wie funktioniert nun der Halbaddierer? Nun, es besteht aus drei logischen Gattern, die and, xorund das nand. Das nand gibt einen positiven Strom, wenn beide Eingänge negativ sind, was bedeutet, dass dies den Fall von 0 und 0 löst. Das xorgibt einen positiven Ausgang, einer der Eingänge ist positiv und der andere negativ, was bedeutet, dass es das Problem von löst 1 und 0. Das andgibt nur dann einen positiven Ausgang, wenn beide Eingänge positiv sind, so dass das Problem von 1 und 1 gelöst ist. Im Grunde haben wir jetzt unseren Halbaddierer. Wir können aber immer noch nur Bits hinzufügen.

Jetzt machen wir unseren Volladdierer. Ein Volladdierer besteht darin, den Halbaddierer immer wieder aufzurufen. Nun hat dies einen Carry. Wenn wir 1 und 1 addieren, erhalten wir einen Übertrag 1. Der Volladdierer nimmt also den Übertrag vom Halbaddierer, speichert ihn und übergibt ihn als weiteres Argument an den Halbaddierer.

Wenn Sie sich nicht sicher sind, wie Sie den Übertrag übergeben können, addieren Sie die Bits zunächst mit dem Halbaddierer und dann mit der Summe und dem Übertrag. Jetzt haben Sie den Übertrag mit den beiden Bits hinzugefügt. Also machst du das immer wieder, bis die Bits, die du hinzufügen musst, vorbei sind und dann bekommst du dein Ergebnis.

Überrascht? So passiert es tatsächlich. Es sieht nach einem langen Prozess aus, aber der Computer erledigt ihn in Bruchteilen einer Nanosekunde oder genauer gesagt in einem halben Taktzyklus. Manchmal wird es sogar in einem einzigen Taktzyklus ausgeführt. Grundsätzlich verfügt der Computer über ALU(einen Großteil CPU), Speicher, Busse usw.

Wenn Sie Computerhardware aus Logikgattern, Speicher und der ALU lernen und einen Computer simulieren möchten, können Sie diesen Kurs sehen, aus dem ich all dies gelernt habe: Bauen Sie einen modernen Computer aus den ersten Prinzipien

Es ist kostenlos, wenn Sie kein E-Zertifikat möchten. Der zweite Teil des Kurses steht im Frühjahr dieses Jahres an

Ashish Ahuja
quelle
11
Ein paar Millisekunden? Für eine einzelne hinzufügen?
JAB
2
Die Addition mit zwei registrierten Werten erfolgt im Allgemeinen in einem einzigen Takt.
Cody Gray
5
@ Tamoghna Chowdhury: Probieren Sie einige Bruchteile einer Nanosekunde aus. Register Add ist IIRC One Clock auf neueren Intel-Prozessoren, also mit einer Taktrate von mehreren GHz ... Und das zählt nicht Pipelining, superskalare Ausführung und so weiter.
Jamesqf
Dieser Ripple-Carry-Addierer würde zu viel Latenz hinzufügen, sodass er in der Hardware nicht einmal auf diese Weise implementiert wird.
Pipe
Der Ripple-Carry-Addierer wird seit Jahrzehnten nicht mehr von CPUs verwendet, da er zu langsam ist. Stattdessen verwenden sie komplexere Addierer, die die Aufgabe in einem einzigen Taktzyklus erledigen können (oder sogar in einem halben Zyklus bei einigen doppelt getakteten ALUs von Intel). (Na ja, die meisten CPUs verwenden Sie es nicht Low-End - Embedded - CPUs noch es für die geringe Anzahl der Transistoren verwenden könnten..)
Mark
15

C verwendet eine abstrakte Maschine, um zu beschreiben, was C-Code tut. Wie es funktioniert, ist also nicht festgelegt. Es gibt C "Compiler", die beispielsweise C tatsächlich in eine Skriptsprache kompilieren.

In den meisten C-Implementierungen werden jedoch +zwischen zwei Ganzzahlen, die kleiner als die Ganzzahlgröße der Maschine sind, (nach vielen Schritten) in eine Assembly-Anweisung übersetzt. Die Montageanweisung wird in Maschinencode übersetzt und in Ihre ausführbare Datei eingebettet. Assembly ist eine Sprache, die aus dem Maschinencode "einen Schritt entfernt" ist und leichter zu lesen sein soll als eine Reihe gepackter Binärdateien.

Dieser Maschinencode (nach vielen Schritten) wird dann von der Zielhardwareplattform interpretiert, wo er vom Befehlsdecoder auf der CPU interpretiert wird. Dieser Befehlsdecoder nimmt den Befehl auf und übersetzt ihn in Signale, die entlang "Steuerleitungen" gesendet werden sollen. Diese Signale leiten Daten von Registern und Speicher durch die CPU, wo die Werte häufig in einer arithmetischen Logikeinheit addiert werden.

Die arithmetische Logikeinheit kann separate Addierer und Multiplikatoren haben oder sie miteinander mischen.

Die arithmetische Logikeinheit verfügt über eine Reihe von Transistoren, die die Additionsoperation ausführen und dann den Ausgang erzeugen. Diese Ausgabe wird über die vom Befehlsdecoder erzeugten Signale geleitet und in einem Speicher oder Registern gespeichert.

Das Layout dieser Transistoren sowohl in der arithmetischen Logikeinheit als auch im Befehlsdecoder (sowie in Teilen, die ich beschönigt habe) wird in der Anlage in den Chip geätzt. Das Ätzmuster wird häufig durch Kompilieren einer Hardwarebeschreibungssprache erzeugt, die eine Abstraktion dessen, was mit was verbunden ist und wie sie arbeiten, nimmt und Transistoren und Verbindungsleitungen erzeugt.

Die Hardwarebeschreibungssprache kann Verschiebungen und Schleifen enthalten, die keine Ereignisse beschreiben zeitliche (wie nacheinander), sondern räumliche beschreiben. Sie beschreibt die Verbindungen zwischen verschiedenen Teilen der Hardware. Dieser Code sieht möglicherweise sehr vage aus wie der Code, den Sie oben gepostet haben.

Das Obige beschönigt viele Teile und Schichten und enthält Ungenauigkeiten. Dies liegt sowohl an meiner eigenen Inkompetenz (ich habe sowohl Hardware als auch Compiler geschrieben, bin aber kein Experte in beiden) und daran, dass vollständige Details eine oder zwei Karrieren erfordern würden und keinen SO-Beitrag.

Hier ist ein SO-Beitrag über einen 8-Bit-Addierer. Hier ist ein Nicht-SO-Beitrag, in dem Sie einige der Addierer bemerken, die nur operator+in der HDL verwendet werden! (Die HDL selbst versteht +und generiert den Addierercode der unteren Ebene für Sie).

Yakk - Adam Nevraumont
quelle
14

Fast jeder moderne Prozessor, der kompilierten C-Code ausführen kann, unterstützt die Ganzzahladdition. Der Code, den Sie gepostet haben, ist eine clevere Möglichkeit, eine Ganzzahladdition durchzuführen, ohne einen Ganzzahl-Add-Opcode auszuführen, aber es ist nicht so, wie eine Ganzzahladdition normalerweise durchgeführt wird. Tatsächlich verwendet die Funktionsverknüpfung wahrscheinlich eine Form der Ganzzahladdition, um den Stapelzeiger anzupassen.

Der Code, den Sie gepostet haben, basiert auf der Beobachtung, dass Sie ihn beim Hinzufügen von x und y in die gemeinsamen Bits und die für x oder y eindeutigen Bits zerlegen können.

Der Ausdruck x & y(bitweises UND) gibt die für x und y gemeinsamen Bits an. Der Ausdruckx ^ y (bitweises exklusives ODER) gibt die Bits an, die für eines von x oder y eindeutig sind.

Die Summe x + y kann umgeschrieben werden als die Summe des Zweifachen der gemeinsamen Bits (da sowohl x als auch y diese Bits beitragen) plus der Bits, die für x oder y eindeutig sind.

(x & y) << 1 ist doppelt so groß wie die Bits, die sie gemeinsam haben (die Linksverschiebung um 1 multipliziert effektiv mit zwei).

x ^ y sind die Bits, die für eines von x oder y eindeutig sind.

Wenn wir also x durch den ersten Wert und y durch den zweiten ersetzen, sollte die Summe unverändert bleiben. Sie können sich den ersten Wert als Übertrag der bitweisen Additionen und den zweiten als niederwertiges Bit der bitweisen Additionen vorstellen.

Dieser Vorgang wird fortgesetzt, bis x Null ist. An diesem Punkt enthält y die Summe.

Tom Karzes
quelle
14

Der Code, den Sie gefunden haben, versucht zu erklären, wie sehr primitive Computerhardware eine "add" -Anweisung implementieren kann. Ich sage "könnte", weil ich garantieren kann, dass diese Methode von keiner CPU verwendet wird, und ich werde erklären, warum.

Im normalen Leben verwenden Sie Dezimalzahlen und haben gelernt, wie man sie hinzufügt: Um zwei Zahlen hinzuzufügen, fügen Sie die niedrigsten zwei Ziffern hinzu. Wenn das Ergebnis kleiner als 10 ist, notieren Sie das Ergebnis und fahren mit der nächsten Ziffernposition fort. Wenn das Ergebnis 10 oder mehr ist, schreiben Sie das Ergebnis minus 10 auf, fahren mit der nächsten Ziffer fort und kaufen. Denken Sie daran, 1 weitere hinzuzufügen. Zum Beispiel: 23 + 37, Sie addieren 3 + 7 = 10, Sie schreiben 0 auf und denken daran, 1 weitere für die nächste Position hinzuzufügen. An der 10er Position addieren Sie (2 + 3) + 1 = 6 und schreiben das auf. Ergebnis ist 60.

Mit Binärzahlen können Sie genau dasselbe tun. Der Unterschied besteht darin, dass die einzigen Ziffern 0 und 1 sind, sodass die einzig möglichen Summen 0, 1, 2 sind. Bei einer 32-Bit-Zahl würden Sie eine Ziffernposition nach der anderen behandeln. Und so würde es wirklich primitive Computerhardware tun.

Dieser Code funktioniert anders. Sie wissen, dass die Summe zweier Binärziffern 2 ist, wenn beide Ziffern 1 sind. Wenn also beide Ziffern 1 sind, würden Sie an der nächsten Binärposition 1 weitere hinzufügen und 0 aufschreiben. Das ist, was die Berechnung von t bewirkt: Es findet alle Stellen Dabei sind beide Binärziffern 1 (das ist das &) und werden an die nächste Ziffernposition (<< 1) verschoben. Dann wird addiert: 0 + 0 = 0, 0 + 1 = 1, 1 + 0 = 1, 1 + 1 ist 2, aber wir schreiben 0 auf. Das macht der Ausschluss oder Operator.

Aber alle Einsen, die Sie an der nächsten Stelle behandeln mussten, wurden nicht behandelt. Sie müssen noch hinzugefügt werden. Deshalb macht der Code eine Schleife: In der nächsten Iteration werden alle zusätzlichen Einsen hinzugefügt.

Warum macht das kein Prozessor so? Weil es eine Schleife ist und Prozessoren keine Schleifen mögen und es langsam ist. Es ist langsam, da im schlimmsten Fall 32 Iterationen erforderlich sind: Wenn Sie der Zahl 0xffffffff (32 1-Bit) 1 hinzufügen, löscht die erste Iteration Bit 0 von y und setzt x auf 2. Die zweite Iteration löscht Bit 1 von y und setzt x auf 4. Und so weiter. Es dauert 32 Iterationen, um das Ergebnis zu erhalten. Jede Iteration muss jedoch alle Bits von x und y verarbeiten, was viel Hardware erfordert.

Ein primitiver Prozessor erledigt die Dinge genauso schnell wie die Dezimalarithmetik, von der niedrigsten bis zur höchsten Position. Es sind ebenfalls 32 Schritte erforderlich, aber jeder Schritt verarbeitet nur zwei Bits plus einen Wert von der vorherigen Bitposition, sodass die Implementierung viel einfacher ist. Und selbst in einem primitiven Computer kann man es sich leisten, dies zu tun, ohne Schleifen implementieren zu müssen.

Eine moderne, schnelle und komplexe CPU verwendet einen "bedingten Summenaddierer". Insbesondere wenn die Anzahl der Bits hoch ist, beispielsweise ein 64-Bit-Addierer, spart dies viel Zeit.

Ein 64-Bit-Addierer besteht aus zwei Teilen: Erstens einem 32-Bit-Addierer für das niedrigste 32-Bit. Dieser 32-Bit-Addierer erzeugt eine Summe und einen "Übertrag" (ein Indikator dafür, dass der nächsten Bitposition eine 1 hinzugefügt werden muss). Zweitens zwei 32-Bit-Addierer für die höheren 32-Bit: Einer addiert x + y, der andere addiert x + y + 1. Alle drei Addierer arbeiten parallel. Wenn der erste Addierer seinen Übertrag erzeugt hat, wählt die CPU nur aus, welches der beiden Ergebnisse x + y oder x + y + 1 das richtige ist, und Sie haben das vollständige Ergebnis. Ein 64-Bit-Addierer dauert also nur ein kleines bisschen länger als ein 32-Bit-Addierer, nicht doppelt so lang.

Die 32-Bit-Addiererteile werden wieder als bedingte Summenaddierer implementiert, wobei mehrere 16-Bit-Addierer verwendet werden, und die 16-Bit-Addierer sind bedingte Summenaddierer und so weiter.

gnasher729
quelle
13

Meine Frage ist: Ist der Operator + als der Code implementiert, der in den meisten Implementierungen veröffentlicht wurde?

Beantworten wir die eigentliche Frage. Alle Operatoren werden vom Compiler als interne Datenstruktur implementiert, die nach einigen Transformationen schließlich in Code übersetzt wird. Sie können nicht sagen, welcher Code durch eine einzelne Addition generiert wird, da fast kein Compiler der realen Welt Code für einzelne Anweisungen generiert.

Dem Compiler steht es frei, Code zu generieren, solange er sich so verhält, als ob die eigentlichen Operationen gemäß dem Standard ausgeführt würden. Aber was tatsächlich passiert, kann etwas völlig anderes sein.

Ein einfaches Beispiel:

static int
foo(int a, int b)
{
    return a + b;
}
[...]
    int a = foo(1, 17);
    int b = foo(x, x);
    some_other_function(a, b);

Hier müssen keine zusätzlichen Anweisungen generiert werden. Es ist für den Compiler völlig legal, dies zu übersetzen in:

some_other_function(18, x * 2);

Oder der Compiler bemerkt, dass Sie die Funktion fooeinige Male hintereinander aufrufen und dass es sich um eine einfache Arithmetik handelt, die Vektoranweisungen dafür generiert. Oder dass das Ergebnis der Addition später für die Array-Indizierung verwendet wird und dielea Anweisung verwendet wird.

Sie können einfach nicht darüber sprechen, wie ein Operator implementiert wird, da er fast nie alleine verwendet wird.

Kunst
quelle
11

Wenn eine Aufschlüsselung des Codes anderen hilft, nehmen Sie das folgende Beispiel x=2, y=6:


xist nicht Null, also füge hinzu zu y:

while(2) {

x & y = 2 weil

        x: 0 0 1 0  //2
        y: 0 1 1 0  //6
      x&y: 0 0 1 0  //2

2 <<1 = 4weil << 1alle Bits nach links verschoben:

      x&y: 0 0 1 0  //2
(x&y) <<1: 0 1 0 0  //4

Zusammengefasst bunkern dieses Ergebnis, 4in tmit

int t = (x & y) <<1;

Wenden Sie nun das bitweise XOR any^=x :

        x: 0 0 1 0  //2
        y: 0 1 1 0  //6
     y^=x: 0 1 0 0  //4

Also x=2, y=4. Zum Schluss summieren Sie, t+yindem Sie zurücksetzen x=tund zum Anfang der whileSchleife zurückkehren:

x = t;

Wenn t=0(oder am Anfang der Schleife x=0), beenden Sie mit

return y;
user1717828
quelle
1
Es gab bereits eine gute Erklärung, warum wir das Übertragsbit verstauen, daher poste ich diese Antwort, um zu zeigen, wie der Code funktioniert.
user1717828
11

Aus Interesse auf dem Atmega328P-Prozessor mit dem avr-g ++ - Compiler implementiert der folgende Code das Hinzufügen eines durch Subtrahieren von -1:

volatile char x;
int main ()
  {
  x = x + 1;  
  }

Generierter Code:

00000090 <main>:
volatile char x;
int main ()
  {
  x = x + 1;  
  90:   80 91 00 01     lds r24, 0x0100
  94:   8f 5f           subi    r24, 0xFF   ; 255
  96:   80 93 00 01     sts 0x0100, r24
  }
  9a:   80 e0           ldi r24, 0x00   ; 0
  9c:   90 e0           ldi r25, 0x00   ; 0
  9e:   08 95           ret

Beachten Sie insbesondere, dass das Hinzufügen von der erfolgt subi Addieren Befehl erfolgt (Konstante vom Register subtrahieren), wobei 0xFF in diesem Fall effektiv -1 ist.

Interessant ist auch, dass dieser spezielle Prozessor keine addiAnweisung hat, was impliziert, dass die Designer dachten, dass ein Subtrahieren des Komplements von den Compiler-Autoren angemessen gehandhabt würde.

Nutzt dies das Komplement von zwei oder andere implementierungsabhängige Funktionen?

Es wäre wahrscheinlich fair zu sagen, dass Compiler-Writer versuchen würden, den gewünschten Effekt (Hinzufügen einer Zahl zu einer anderen) auf die effizienteste Art und Weise zu implementieren, die für diese bestimmte Architektur möglich ist. Wenn dies das Subtrahieren des Komplements erfordert, soll es so sein.

Nick Gammon
quelle