Ein solcher Code kann viele Vorteile haben, aber leider wurde der C-Standard nicht geschrieben, um ihn zu vereinfachen. Compiler haben in der Vergangenheit effektive Verhaltensgarantien geboten, die über die Anforderungen des Standards hinausgehen und es ermöglichten, solchen Code viel sauberer zu schreiben als dies in Standard C möglich ist. In letzter Zeit widerrufen Compiler solche Garantien jedoch im Namen der Optimierung.
Insbesondere haben viele C-Compiler in der Vergangenheit garantiert (von Entwurf bis Dokumentation), dass, wenn zwei Strukturtypen dieselbe anfängliche Sequenz enthalten, ein Zeiger auf einen der beiden Typen verwendet werden kann, um auf Mitglieder dieser gemeinsamen Sequenz zuzugreifen, auch wenn die Typen nicht in Beziehung stehen. und weiter, dass zum Zwecke des Erstellens einer gemeinsamen Anfangssequenz alle Zeiger auf Strukturen äquivalent sind. Code, der von einem solchen Verhalten Gebrauch macht, kann viel sauberer und typsicherer sein als Code, der dies nicht tut. Obwohl der Standard jedoch vorschreibt, dass Strukturen, die eine gemeinsame Anfangssequenz haben, auf die gleiche Weise angelegt werden müssen, verbietet er die tatsächliche Verwendung von Code Ein Zeiger eines Typs, um auf die ursprüngliche Sequenz eines anderen Typs zuzugreifen.
Wenn Sie objektorientierten Code in C schreiben möchten, müssen Sie sich (und sollten Sie diese Entscheidung frühzeitig treffen) entscheiden, entweder durch viele Rahmen zu springen, um die Zeigerregeln von C einzuhalten, und sich darauf vorzubereiten moderne Compiler generieren unsinnigen Code, wenn man aus dem Ruder läuft, auch wenn ältere Compiler Code generiert hätten, der wie vorgesehen funktioniert, oder dokumentieren eine Anforderung, dass der Code nur mit Compilern verwendet werden kann, die so konfiguriert sind, dass sie das Verhalten von Zeigern im alten Stil unterstützen (z. B. mit ein "-fno-strict-aliasing") Einige Leute betrachten "-fno-strict-aliasing" als böse, aber ich würde vorschlagen, dass es hilfreicher ist, sich "-fno-strict-aliasing" C als eine Sprache vorzustellen, die bietet für einige Zwecke eine größere semantische Kraft als "Standard" C,aber auf Kosten von Optimierungen, die für andere Zwecke wichtig sein könnten.
Bei herkömmlichen Compilern interpretieren historische Compiler beispielsweise den folgenden Code:
struct pair { int i1,i2; };
struct trio { int i1,i2,i3; };
void hey(struct pair *p, struct trio *t)
{
p->i1++;
t->i1^=1;
p->i1--;
t->i1^=1;
}
B. Ausführen der folgenden Schritte in der Reihenfolge: Inkrementieren des ersten Elements von *p
, Ergänzen des niedrigsten Bits des ersten Elements von *t
, Dekrementieren des ersten Elements von *p
und Ergänzen des niedrigsten Bits des ersten Elements von *t
. Moderne Compiler ordnen die Abfolge der Operationen auf eine Weise neu, die effizienter ist, wenn p
und t
verschiedene Objekte identifiziert werden, ändern jedoch das Verhalten, wenn dies nicht der Fall ist .
Dieses Beispiel wurde natürlich absichtlich erfunden, und in der Praxis funktioniert Code, der einen Zeiger eines Typs verwendet, um auf Mitglieder zuzugreifen, die Teil der allgemeinen Anfangssequenz eines anderen Typs sind, normalerweise , aber leider gibt es keine Möglichkeit zu wissen, wann ein solcher Code fehlschlagen könnte Die sichere Verwendung ist nur durch Deaktivieren der typbasierten Aliasing-Analyse möglich.
Ein etwas weniger ausgeklügeltes Beispiel würde auftreten, wenn man eine Funktion schreiben möchte, um so etwas wie zwei Zeiger auf beliebige Typen zu tauschen. In der überwiegenden Mehrheit der "1990er C" -Compiler konnte dies erreicht werden durch:
void swap_pointers(void **p1, void **p2)
{
void *temp = *p1;
*p1 = *p2;
*p2 = temp;
}
In Standard C müsste man jedoch verwenden:
#include "string.h"
#include "stdlib.h"
void swap_pointers2(void **p1, void **p2)
{
void **temp = malloc(sizeof (void*));
memcpy(temp, p1, sizeof (void*));
memcpy(p1, p2, sizeof (void*));
memcpy(p2, temp, sizeof (void*));
free(temp);
}
Wenn *p2
der temporäre Zeiger im zugewiesenen Speicher verbleibt und nicht im zugewiesenen Speicher, wird der effektive Typ von *p2
zum Typ des temporären Zeigers und der Code, der versucht, ihn *p2
als einen beliebigen Typ zu verwenden, der nicht mit dem temporären Zeiger übereinstimmt type ruft Undefiniertes Verhalten auf. Es ist zwar äußerst unwahrscheinlich, dass ein Compiler so etwas bemerkt, aber da die moderne Compilerphilosophie verlangt, dass Programmierer um jeden Preis Undefiniertes Verhalten vermeiden, kann ich mir keine andere sichere Methode zum Schreiben des obigen Codes vorstellen, ohne zugewiesenen Speicher zu verwenden .