Enthalten bemerkenswerte C-Erweiterungen Integer-Typen, deren Verhalten von der Größe des Maschinenworts unabhängig ist?

12

Ein interessantes Merkmal von C im Vergleich zu einigen anderen Sprachen ist, dass viele seiner Datentypen auf der Wortgröße der Zielarchitektur basieren und nicht absolut angegeben werden. Auf diese Weise kann die Sprache zwar zum Schreiben von Code auf Computern verwendet werden, auf denen bestimmte Typen möglicherweise Schwierigkeiten haben, es ist jedoch sehr schwierig, Code zu entwerfen, der auf verschiedenen Architekturen konsistent ausgeführt wird. Betrachten Sie den Code:

uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;

Auf einer Architektur mit int16 Bit (gilt immer noch für viele kleine Mikrocontroller) würde dieser Code unter Verwendung eines genau definierten Verhaltens den Wert 1 zuweisen. Auf Computern mit int64 Bit wird ein Wert von 4294836225 zugewiesen, wobei wiederum ein genau definiertes Verhalten verwendet wird. Auf Rechnern mit int32 Bit wird wahrscheinlich ein Wert von -131071 zugewiesen (ich weiß nicht, ob dies ein implementierungsdefiniertes oder ein nicht definiertes Verhalten ist). Obwohl der Code nichts anderes verwendet als nominell "festgelegte" Typen, würde der Standard erfordern, dass zwei verschiedene Arten von Compilern, die heute verwendet werden, zwei unterschiedliche Ergebnisse liefern, und viele populäre Compiler würden heute ein drittes liefern.

Dieses spezielle Beispiel ist insofern etwas ausgeklügelt, als ich nicht erwarten würde, dass im Code der realen Welt das Produkt zweier 16-Bit-Werte direkt einem 64-Bit-Wert zugewiesen wird, sondern es wurde als kurzes Beispiel ausgewählt, um drei Arten von Ganzzahlen zu zeigen Werbeaktionen können mit unsignierten Typen mit angeblich fester Größe interagieren. Es gibt Situationen in der Praxis, in denen Mathematik für vorzeichenlose Typen nach den Regeln der mathematischen Ganzzahlarithmetik ausgeführt werden muss, andere, in denen sie nach den Regeln der modularen Arithmetik ausgeführt werden muss, und solche, in denen dies nicht der Fall ist. t egal. Ein Großteil des realen Codes für Dinge wie Prüfsummen beruht auf dem uint32_tArithmetic Wrapping Mod 2³² und der Fähigkeit, eine beliebige Leistung zu erbringenuint16_t arithmetik und erhalte ergebnisse, die mindestens als genau definiert sind mod 65536 (im gegensatz zum auslösen von undefiniertem verhalten).

Auch wenn diese Situation offensichtlich unerwünscht erscheint (und in zunehmendem Maße dazu führen wird, dass die 64-Bit-Verarbeitung für viele Zwecke zur Norm wird), zieht es das C-Normenkomitee nach meinen Beobachtungen vor, Sprachfunktionen einzuführen, die bereits in einigen bemerkenswerten Produktionen verwendet werden Umgebungen, anstatt sie "von Grund auf neu" zu erfinden. Gibt es bemerkenswerte Erweiterungen der C-Sprache, mit denen der Code nicht nur festlegen kann, wie ein Typ gespeichert wird, sondern auch, wie er sich in Szenarien mit möglichen Werbeaktionen verhalten soll? Ich sehe mindestens drei Möglichkeiten, wie eine Compilererweiterung solche Probleme lösen könnte:

  1. Durch Hinzufügen einer Direktive, die den Compiler anweist, bestimmte "grundlegende" Ganzzahltypen auf bestimmte Größen zu zwingen.

  2. Durch Hinzufügen einer Direktive, die den Compiler anweist, verschiedene Heraufstufungsszenarien zu bewerten, als ob die Maschinentypen bestimmte Größen hätten, unabhängig von den tatsächlichen Größen der Typen in der Zielarchitektur.

  3. Indem Sie zulassen, dass Typen mit bestimmten Merkmalen deklariert werden (z. B. deklarieren, dass ein Typ sich unabhängig von der zugrunde liegenden Wortgröße wie ein Mod-65536-Umbruch-Algebra-Ring verhalten soll und nicht implizit in andere Typen konvertierbar sein soll; Hinzufügen von a wrap32zu an intsollte a ergeben) Das Ergebnis des Typs, wrap32unabhängig davon, ob intes größer als 16 Bit ist, während das wrap32direkte Hinzufügen eines zu einem wrap16ungültig sein sollte (da keines in das andere konvertieren kann).

Meine eigene Präferenz wäre die dritte Alternative, da selbst Maschinen mit ungewöhnlichen Wortgrößen mit viel Code arbeiten könnten, bei dem erwartet wird, dass Variablen wie bei Größen mit Zweierpotenzen "umbrochen" werden. Der Compiler muss möglicherweise Bitmaskierungsanweisungen hinzufügen, damit sich der Typ angemessen verhält. Wenn der Code jedoch einen Typ benötigt, der Mod 65536 umschließt, ist es besser, wenn der Compiler eine solche Maskierung auf Maschinen generiert, die dies benötigen, als den Quellcode damit zu überladen oder einfach einen solchen Code haben, der auf Maschinen unbrauchbar ist, auf denen eine solche Maskierung erforderlich wäre. Ich bin jedoch neugierig, ob es übliche Erweiterungen gibt, die mit einem der oben genannten Mittel oder mit Mitteln, an die ich nicht gedacht habe, tragbares Verhalten erzielen.

Um zu verdeutlichen, wonach ich suche, gibt es ein paar Dinge; vor allem:

  1. Es gibt zwar viele Möglichkeiten, wie Code geschrieben werden kann, um die gewünschte Semantik sicherzustellen (z. B. das Definieren von Makros für die Ausführung von Berechnungen für nicht vorzeichenbehaftete Operanden bestimmter Größe, um ein Ergebnis zu erzielen, das explizit umbrochen wird oder nicht) oder zumindest unerwünschte Ereignisse verhindert Semantik (z. B. bedingte Definition eines Typs wrap32_tfür uint32_tCompiler, auf denen a uint32_tnicht heraufgestuft wird, und Angabe, dass es besser für Code ist, bei dem die wrap32_tKompilierung auf Computern fehlschlagen muss, auf denen dieser Typ heraufgestuft wird, als dass er ausgeführt wird und ein falsches Verhalten ergibt), Wenn es eine Möglichkeit gibt, den Code zu schreiben, der für zukünftige Spracherweiterungen am besten geeignet ist, ist es besser, diesen Code zu verwenden, als meinen eigenen Ansatz zu entwickeln.

  2. Ich habe einige ziemlich solide Ideen, wie die Sprache erweitert werden könnte, um viele ganzzahlige Probleme zu lösen, sodass der Code auf Computern mit unterschiedlichen Wortgrößen die gleiche Semantik ergibt, aber bevor ich sie aufschreibe, möchte ich zu wissen, welche Anstrengungen in diese Richtung bereits unternommen wurden.

Ich möchte in keiner Weise den C-Normenausschuss oder die von ihm geleistete Arbeit herabsetzen. Ich gehe jedoch davon aus, dass es in einigen Jahren erforderlich sein wird, den Code auf Computern, auf denen der "natürliche" Promotion-Typ 32 Bit beträgt, sowie auf Computern, auf denen er 64 Bit beträgt, ordnungsgemäß auszuführen. Ich denke, mit einigen bescheidenen Erweiterungen der Sprache (bescheidener als viele der anderen Änderungen zwischen C99 und C14) wäre es möglich, nicht nur eine saubere Art der effizienten Verwendung von 64-Bit-Architekturen bereitzustellen, sondern auch die Interaktion mit ihnen zu vereinfachen die „Unusual-Wort-size“ Maschinen , die die Standard - historisch umgebogenen rückwärts Träger [eg es möglich , dass ein Maschine mit einem 12-Bit - Herstellung charzu laufen Code, der eine erwartetuint32_tum mod 2³² zu wickeln]. Abhängig von der Richtung, in die zukünftige Erweiterungen gehen, würde ich auch erwarten, dass es möglich sein sollte, Makros zu definieren, mit denen heute geschriebener Code auf heutigen Compilern verwendet werden kann, bei denen sich die Standard-Integer-Typen wie "erwartet" verhalten, aber auch auf zukünftigen Compilern, bei denen Integer verwendet werden können Typen würden sich standardmäßig anders verhalten, aber wo können die erforderlichen Verhaltensweisen zur Verfügung gestellt werden.

Superkatze
quelle
4
@RobertHarvey Bist du sicher? Wie ich verstehe integer Förderung , wenn intgrößer als uint16_twürden die Operanden der Multiplikation zu fördern intund die Multiplikation durchgeführt werden soll , wie intMultiplikation und der resultierende intWert umgewandelt würde int64_tdie für die Initialisierung who_knows.
3
@RobertHarvey Wie? In OPs Code gibt es keine Erwähnung von int, aber es schleicht sich immer noch hinein. (Wieder unter der Annahme, dass mein Verständnis des C-Standards korrekt ist.)
2
@RobertHarvey Sicher, es hört sich schlecht an, aber wenn Sie nicht darauf hinweisen können, tragen Sie nichts dazu bei, indem Sie sagen: "Nein, Sie müssen etwas falsch machen." Die eigentliche Frage ist, wie man die ganzzahlige Promotion vermeidet oder ihre Auswirkungen umgeht!
3
@RobertHarvey: Eines der historischen Ziele des C-Normungsausschusses war es, nahezu jedem Computer einen "C-Compiler" zu ermöglichen und die Regeln so spezifisch zu machen, dass unabhängig entwickelte C-Compiler für einen bestimmten Zielcomputer dies tun würden meistens austauschbar sein. Dies wurde durch die Tatsache erschwert, dass Menschen damit begannen, C-Compiler für viele Maschinen zu schreiben, bevor die Standards ausgearbeitet wurden, und das Standards Committee wollte Compilern nicht verbieten, irgendetwas zu tun, auf das sich bestehender Code stützen könnte . Einige ziemlich grundlegende Aspekte des Standards ...
Supercat
3
... sind so, wie sie sind, nicht weil irgendjemand versucht hat, ein Regelwerk zu formulieren, das "Sinn ergibt", sondern weil das Komitee versucht hat, all die Dinge festzunageln, die die unabhängig geschriebenen Compiler , die es bereits gab, gemeinsam hatten. Leider hat dieser Ansatz zu Standards geführt, die gleichzeitig zu vage sind, um es Programmierern zu ermöglichen, zu spezifizieren, was zu tun ist, aber zu spezifisch, um es Compilern zu ermöglichen, "es einfach zu tun".
Supercat

Antworten:

4

Als die typische Absicht von Code wie folgt

uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;

Um die Multiplikation in 64 Bit durchzuführen (die Größe der Variablen, in der das Ergebnis gespeichert wird), müssen Sie normalerweise einen der Operanden verwenden, um eine 64-Bit-Multiplikation zu erzwingen:

uint16_t ffff16 = 0xFFFF;
int64_t i_know = (int64_t)ffff16 * ffff16;

Ich habe noch nie C-Erweiterungen gefunden, die diesen Vorgang automatisieren.

Bart van Ingen Schenau
quelle
1
Meine Frage war nicht, wie man die korrekte Auswertung eines bestimmten arithmetischen Ausdrucks erzwingt (je nachdem, welches Ergebnis man haben möchte, entweder einen Operanden in uint32_tein Makro umwandeln oder ein Makro verwenden, das als eines #define UMUL1616to16(x,y)((uint16_t)((uint16_t)(x)*(uint16_t)(y)))oder #define UMUL1616to16(x,y)((uint16_t)((uint32_t)(x)*(uint16_t)(y)))abhängig von der Größe von definiert ist int), sondern ob es welche gibt neue Standards für den sinnvollen Umgang mit solchen Dingen, anstatt meine eigenen Makros zu definieren.
Supercat
Ich hätte auch erwähnen sollen, dass für Dinge wie Hashing und Prüfsummenberechnungen der Zweck oft darin besteht, ein Ergebnis zu nehmen und es auf die Größe der Operanden zu kürzen. Die typische Absicht eines Ausdrucks wie ist (ushort1*ushort2) & 65535ues, eine Mod-65536-Arithmetik für alle Operandenwerte durchzuführen. Wenn man die C89-Begründung liest, ist es meines Erachtens ziemlich klar, dass die Autoren zwar erkannten, dass ein solcher Code bei einigen Implementierungen fehlschlagen könnte, wenn das Ergebnis 2147483647 überschreitet, sie jedoch damit rechnen, dass solche Implementierungen zunehmend seltener werden. Ein solcher Code schlägt jedoch manchmal auf modernen gcc fehl.
Superkatze