Warum ist diese angebliche dereferenzierende typgesteuerte Zeigerwarnung compilerspezifisch?




pointers casting (4)

Ich habe various posts on Stack Overflow RE gelesen: den vom Typ gemachten Zeigerfehler beim Dereferenzieren. Ich verstehe, dass der Fehler im Wesentlichen die Compiler-Warnung vor der Gefahr des Zugriffs auf ein Objekt durch einen Zeiger eines anderen Typs ist (obwohl für char* anscheinend eine Ausnahme gemacht wird). char* ist eine verständliche und vernünftige Warnung.

Meine Frage bezieht sich speziell auf den folgenden Code: Warum ist das Umsetzen der Adresse eines Zeigers auf eine void** für diese Warnung -Werror (durch -Werror in einen Fehler -Werror )?

Darüber hinaus ist dieser Code für mehrere Zielarchitekturen kompiliert, von denen nur eine die Warnung / den Fehler generiert - könnte dies bedeuten, dass es sich legitimerweise um einen versionsspezifischen Fehler des Compilers handelt?

// main.c
#include <stdlib.h>

typedef struct Foo
{
  int i;
} Foo;

void freeFunc( void** obj )
{
  if ( obj && * obj )
  {
    free( *obj );
    *obj = NULL;
  }
}

int main( int argc, char* argv[] )
{
  Foo* f = calloc( 1, sizeof( Foo ) );
  freeFunc( (void**)(&f) );

  return 0;
}

Wenn mein oben angegebenes Verständnis richtig ist, eine void** , die immer noch nur ein Hinweis ist, sollte dies ein sicherer Wurf sein.

Gibt es eine Problemumgehung , bei der keine I-Werte verwendet werden , um diese compilerspezifische Warnung / Fehler zu beheben ? Dh ich verstehe das und warum dies das Problem lösen wird, aber ich möchte diesen Ansatz vermeiden, weil ich freeFunc() NULL nutzen möchte, um ein beabsichtigtes Out-Argument zu erhalten:

void* tmp = f;
freeFunc( &tmp );
f = NULL;

Problem Compiler (einer von einem):

[email protected]:/build$ /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc --version && /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc -Wall -O2 -Werror ./main.c
i686-fc3-linux-gnu-gcc (GCC) 3.4.5
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

./main.c: In function `main':
./main.c:21: warning: dereferencing type-punned pointer will break strict-aliasing rules

[email protected]:/build$

Nicht klagender Compiler (einer von vielen):

[email protected]:/build$ /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc --version && /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc -Wall -O2 -Werror ./main.c
i686-rh73-linux-gnu-gcc (GCC) 3.2.3
Copyright (C) 2002 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

[email protected]:/build$

Update: Ich habe außerdem festgestellt, dass die Warnung speziell beim Kompilieren mit -O2 generiert wird (immer noch nur mit dem angegebenen "Problem-Compiler").


Die Dereferenzierung eines typgesteuerten Zeigers ist UB und Sie können sich nicht darauf verlassen, was passieren wird.

Verschiedene Compiler erzeugen unterschiedliche Warnungen, und zu diesem Zweck können verschiedene Versionen desselben Compilers als unterschiedliche Compiler betrachtet werden. Dies scheint eine bessere Erklärung für die Varianz zu sein als eine Abhängigkeit von der Architektur.

Ein Fall, der Ihnen helfen kann, zu verstehen, warum das Schreiben in diesem Fall schlecht sein kann, ist, dass Ihre Funktion auf einer Architektur, für die sizeof(Foo*) != sizeof(void*) gilt, nicht funktioniert. Das ist durch die Norm autorisiert, obwohl ich keine aktuelle kenne, für die dies zutrifft.

Eine Problemumgehung wäre die Verwendung eines Makros anstelle einer Funktion.

Beachten Sie, dass free Nullzeiger akzeptiert.


Dieser Code ist gemäß C-Standard ungültig, funktioniert also möglicherweise in einigen Fällen, ist jedoch nicht unbedingt portierbar.

Die "strenge Aliasing-Regel" für den Zugriff auf einen Wert über einen Zeiger, der in einen anderen Zeigertyp umgewandelt wurde, ist in 6.5, Absatz 7 zu finden:

Auf den gespeicherten Wert eines Objekts darf nur mit einem Wertausdruck eines der folgenden Typen zugegriffen werden:

  • einen Typ, der mit dem effektiven Typ des Objekts kompatibel ist,

  • eine qualifizierte Version eines Typs, der mit dem effektiven Typ des Objekts kompatibel ist,

  • ein Typ, der der vorzeichenbehaftete oder vorzeichenlose Typ ist, der dem effektiven Typ des Objekts entspricht,

  • ein Typ, der der vorzeichenbehaftete oder vorzeichenlose Typ ist, der einer qualifizierten Version des effektiven Typs des Objekts entspricht,

  • einen Aggregat- oder Vereinigungstyp, der einen der oben genannten Typen unter seinen Mitgliedern umfasst (einschließlich rekursiv eines Mitglieds einer Untergemeinschaft oder einer geschlossenen Vereinigung), oder

  • ein Zeichentyp.

In Ihrem *obj = NULL; Anweisung, das Objekt hat den effektiven Typ Foo* , wird aber vom lvalue-Ausdruck *obj mit dem Typ void* *obj .

In 6.7.5.1 Absatz 2 haben wir

Damit zwei Zeigertypen kompatibel sind, müssen beide identisch qualifiziert sein und beide müssen Zeiger auf kompatible Typen sein.

Daher sind void* und Foo* keine kompatiblen Typen oder Typen mit hinzugefügten Qualifikationsmerkmalen und passen auf keinen Fall zu den anderen Optionen der strengen Aliasing-Regel.

Obwohl dies nicht der technische Grund ist, warum der Code ungültig ist, ist es auch relevant, Abschnitt 6.2.5 Absatz 26 zu beachten:

Ein Zeiger auf void muss die gleichen Darstellungs- und Ausrichtungsanforderungen haben wie ein Zeiger auf einen Zeichentyp. Ebenso müssen Zeiger auf qualifizierte oder nicht qualifizierte Versionen kompatibler Typen die gleichen Darstellungs- und Ausrichtungsanforderungen haben. Alle Zeiger auf Strukturtypen müssen die gleichen Darstellungs- und Ausrichtungsanforderungen haben. Alle Zeiger auf Verbindungstypen müssen die gleichen Darstellungs- und Ausrichtungsanforderungen haben. Zeiger auf andere Typen müssen nicht die gleichen Darstellungs- oder Ausrichtungsanforderungen haben.

Was die Unterschiede bei den Warnungen betrifft, ist dies kein Fall, in dem der Standard eine Diagnosemeldung erfordert. Es geht also nur darum, wie gut der Compiler oder seine Version potenzielle Probleme erkennt und auf hilfreiche Weise darauf hinweist. Sie haben bemerkt, dass Optimierungseinstellungen einen Unterschied machen können. Dies liegt häufig daran, dass intern mehr Informationen darüber generiert werden, wie verschiedene Teile des Programms in der Praxis tatsächlich zusammenpassen, und dass zusätzliche Informationen daher auch für Warnprüfungen verfügbar sind.


Eine void * wird vom C-Standard teilweise speziell behandelt, weil sie auf einen unvollständigen Typ verweist. Diese Behandlung gilt nicht für void ** da sie auf einen vollständigen Typ void * , insbesondere void * .

Die strengen Aliasing-Regeln besagen, dass Sie einen Zeiger eines Typs nicht in einen Zeiger eines anderen Typs konvertieren und diesen Zeiger anschließend dereferenzieren können, da dies bedeutet, dass die Bytes eines Typs als andere neu interpretiert werden. Die einzige Ausnahme ist die Konvertierung in einen Zeichentyp, mit dem Sie die Darstellung eines Objekts lesen können.

Sie können diese Einschränkung umgehen, indem Sie anstelle einer Funktion ein funktionsähnliches Makro verwenden:

#define freeFunc(obj) (free(obj), (obj) = NULL)

Was Sie so nennen können:

freeFunc(f);

Dies hat jedoch eine Einschränkung, da das obige Makro obj zweimal auswertet. Wenn Sie GCC verwenden, kann dies mit einigen Erweiterungen vermieden werden, insbesondere mit den Schlüsselworttyp- und Anweisungsausdrücken:

#define freeFunc(obj) ({ typeof (&(obj)) ptr = &(obj); free(*ptr); *ptr = NULL; })

Obwohl C für Maschinen entwickelt wurde, die für alle Zeiger dieselbe Darstellung verwenden, wollten die Autoren des Standards die Sprache für Maschinen verwenden, die unterschiedliche Darstellungen für Zeiger auf verschiedene Objekttypen verwenden. Daher war es nicht erforderlich, dass Maschinen, die unterschiedliche Zeigerdarstellungen für unterschiedliche Zeigertypen verwenden, einen Zeigertyp "auf einen beliebigen Zeigertyp" unterstützen, obwohl dies bei vielen Maschinen zu Nullkosten möglich ist.

Bevor der Standard geschrieben wurde, erlaubten Implementierungen für Plattformen, die für alle Zeigertypen die gleiche Darstellung verwendeten, einstimmig die Verwendung einer void** , zumindest bei geeignetem Casting, als "Zeiger auf einen beliebigen Zeiger". Die Autoren des Standards erkannten mit ziemlicher Sicherheit, dass dies auf Plattformen nützlich sein würde, die ihn unterstützten, aber da er nicht allgemein unterstützt werden konnte, lehnten sie es ab, ihn zu beauftragen. Stattdessen erwarteten sie, dass die Qualitätsimplementierung solche Konstrukte verarbeiten würde, wie sie in der Begründung als "populäre Erweiterung" beschrieben würden, falls dies sinnvoll wäre.





casting