2017-02-03 2 views
4

Führt der folgende Snippet test_func undefiniertes Verhalten unter den strengen Aliasregeln aus, wenn sich die beiden Argumente teilweise überschneiden?Wird beim Verwenden von Zeigern auf Strukturelemente strikter Alias ​​angewendet?

Das ist das zweite Argument ein Mitglied der ersten ist:

#include <stdio.h> 

typedef struct 
{ 
    //... Other fields 
    int x; 
    //... Other fields 
} A; 

int test_func(A *a, int *x) 
{ 
    a->x = 0; 
    *x = 1; 
    return a->x; 
} 

int main() 
{ 
    A a = {0}; 

    printf("%d\n", test_func(&a, &a.x)); 

    return 0; 
} 

Ist der Compiler test_func zu denken erlaubt wird nur 0 zurück, basierend auf der Annahme, dass A* und int* wird nicht Alias? also kann die *x das Mitglied nicht überschreiben?

+1

Ich denke nicht, weil dies der Zweck des Schlüsselwortes ['restrict'] (https://en.wikipedia.org/wiki/Restrict) in C. – Stargateur

+0

ist Ich dachte, dass * strikter Aliasing * auf die verwiesen Fall der Konvertierung zwischen Zeigertypen, unter der Annahme, dass die Typen von gleicher Größe waren. Zum Beispiel das Konvertieren von 'int *' nach 'float *' auf einer Plattform mit 'sizeof int == sizeof float'. Und ich dachte, dass diese Einschränkung auf die Tatsache zurückzuführen ist, dass jede Plattform unterschiedliche Ausrichtungsanforderungen hat, was in bestimmten Szenarien zu undefiniertem Verhalten führen kann. Ich sehe nichts davon in deinem Code. –

+2

Als Faustregel kann man sagen, dass es nur dann eine "strikte Alias-Verletzung" geben kann, wenn Sie einen Zeiger auf einen anderen Typ setzen oder ihn in 'void *' und dann in einen anderen Typ konvertieren. Hier in Ihrem Fall sind alle Typinformationen für den Compiler vorhanden, so dass keine Probleme auftreten dürfen. –

Antwort

4

Dies ist keine Verletzung des strengen Aliasing. Die strenge Aliasing-Regel besagt (vereinfacht), dass Sie nur mit einem Lvalue-Ausdruck eines kompatiblen Typs auf den Wert eines Objekts zugreifen können. In diesem Fall ist das Objekt, auf das Sie zugreifen, das Element x von maina Variable. Dieses Mitglied hat den Typ int. Und der Ausdruck, den Sie verwenden, um darauf zuzugreifen (*x), hat auch den Typ int. Also, es gibt kein Problem.

Sie können strenges Aliasing mit restrict verwechseln. Wenn Sie das Schlüsselwort restrict in der Deklaration eines der Zeigerparameter verwendet hätten, wäre der Code ungültig, weil restrict Sie daran hindert, verschiedene Zeiger für den Zugriff auf dasselbe Objekt zu verwenden - dies ist jedoch ein anderes Problem als das strikte Aliasing.

+0

Die Art und Weise, wie gcc strict aliasing interpretiert, mit 'struct foo {int x;}; Strukturleiste {int x;}; Gewerkschaft {struct foo vf; Strukturbalken fb;}; int test (Struktur foo * pf; Strukturbalken * pb) {pf-> x = 1; pb-> x = 2; Rückgabe pf-> x; } 'es wird Code erzeugt, der annimmt, dass pf-> x' und' pb-> x' keinen Aliasnamen annehmen können, obwohl beide Strukturen Mitglieder des gleichen Union-Typs sind, dessen vollständige Definition am Zugriffspunkt sichtbar ist. Ich glaube nicht, dass die Standards vernünftig interpretiert werden können, um ein solches Verhalten zuzulassen (es würde die Anforderung, dass der "vollständige Unionstyp" sichtbar sein würde, bedeutungslos machen) ... – supercat

+0

... aber die Autoren von gcc haben angegeben, dass sie keine Absicht haben der Unterstützung der CIS-Regel für Zugriffe, die nicht über einen Zeiger des Union-Typs erfolgen (auch wenn der Union-Typ sichtbar ist und die durch die fraglichen Zeiger identifizierten Objekte - oder sogar Mitglieder derselben Gewerkschaft). – supercat

5

Strict aliasing bezieht sich auf, wenn ein Zeiger in einen anderen Zeigertyp konvertiert wird, nach dem auf den Inhalt zugegriffen wird. Strenges Aliasing bedeutet, dass die beteiligten spitzen Typen kompatibel sein müssen. Das trifft hier nicht zu.

Es gibt jedoch den Begriff Pointer Aliasing, was bedeutet, dass zwei Zeiger auf denselben Speicher verweisen können. Der Compiler darf nicht davon ausgehen, dass dies hier der Fall ist. Wenn es Optimierungen wie die von Ihnen beschriebenen durchführen möchte, müsste es möglicherweise Maschinencode hinzufügen, der die Zeiger miteinander vergleicht, um festzustellen, ob sie identisch sind oder nicht. Was an sich die Funktion etwas langsamer machen würde.

Um den Compiler zu helfen, solchen Code zu optimieren, können Sie die Zeiger als restrict deklarieren, was dem Compiler mitteilt, dass der Programmierer garantiert, dass die Zeiger nicht auf den gleichen Speicher zeigen.

Ihre Funktion mit gcc -O3 Ergebnisse in dieser Maschinencode kompiliert:

0x00402D09 mov $0x1,%edx 

was im Grunde bedeutet, dass die ganze Funktion ersetzt wurde (inlined) mit "a.x auf 1 gesetzt".

Aber wenn ich Ihre Funktion als

int test_func(A* restrict a, int* restrict x) 
{ 
    a->x = 0; 
    *x = 1; 
    return a->x; 
} 

neu schreiben und kompilieren mit gcc -O3, tut es 0 zurück, weil ich jetzt den Compiler gesagt haben, dass a->X und x nicht auf dem gleichen Speicher zeigen, so kann es Nehmen Sie an, dass *x = 1; das Ergebnis nicht beeinflusst und überspringen Sie die Zeile *x = 1; oder Sequenz vor der Zeile a->x = 0;.

Der optimierte Maschinencode der Restricted-Version überspringt tatsächlich den gesamten Funktionsaufruf, da er weiß, dass der Wert gemäß Ihrer Initialisierung bereits 0 ist.

Dies ist natürlich ein Fehler, aber der Programmierer ist dafür verantwortlich, für die sorglose Verwendung von restrict.

Verwandte Themen