Forum: Compiler & IDEs strict-aliasing rules


von gast (Gast)


Lesenswert?

Auszug aus dem FreeRTOS:
1
struct xLIST_ITEM
2
{
3
  portTickType xItemValue;        /*< The value being listed.  In most cases this is used to sort the list in descending order. */
4
  volatile struct xLIST_ITEM * pxNext;  /*< Pointer to the next xListItem in the list. */
5
  volatile struct xLIST_ITEM * pxPrevious;/*< Pointer to the previous xListItem in the list. */
6
  void * pvOwner;              /*< Pointer to the object (normally a TCB) that contains the list item.  There is therefore a two way link between the object containing the list item and the list item itself. */
7
  void * pvContainer;            /*< Pointer to the list in which this list item is placed (if any). */
8
};
9
typedef struct xLIST_ITEM xListItem;    /* For some reason lint wants this as two separate definitions. */
10
11
struct xMINI_LIST_ITEM
12
{
13
  portTickType xItemValue;
14
  volatile struct xLIST_ITEM *pxNext;
15
  volatile struct xLIST_ITEM *pxPrevious;
16
};
17
typedef struct xMINI_LIST_ITEM xMiniListItem;
18
19
/*
20
 * Definition of the type of queue used by the scheduler.
21
 */
22
typedef struct xLIST
23
{
24
  volatile unsigned portBASE_TYPE uxNumberOfItems;
25
  volatile xListItem * pxIndex;      /*< Used to walk through the list.  Points to the last item returned by a call to pvListGetOwnerOfNextEntry (). */
26
  volatile xMiniListItem xListEnd;    /*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
27
} xList;
Bei folgender Zeile bekomme ich eine Warnung bezüglich der benützung von 
strict-aliasing rules:
1
  pxList->xListEnd.pxNext = ( xListItem * ) &( pxList->xListEnd );


dereferencing type-punned pointer will break strict-aliasing rules


Wie kann man denn diese Warnung beheben?

mfg. ein Gast

von yalu (Gast)


Lesenswert?

Entweder

  -fno-strict-aliasing als Option für GCC angeben (Nachteil: manchmal
  schlechteres Optimierungsergebnis)

oder

  Optimierung auf maximal -O1 beschränken (Nachteil: fast immer
  schlechteres Optimierungsergebnis)

oder

  wilde Pointer-Casts vermeiden (Nachteil: machmal Denkarbeit
  erforderlich ;-))

von gast (Gast)


Lesenswert?

Und genau auf Antwortmöglichkeit 3 möchte ich hinaus, nur scheitert es 
da an meinen C Kenntnissesn.

von Simon K. (simon) Benutzerseite


Lesenswert?

gast wrote:
> Auszug aus dem FreeRTOS:
>
1
> struct xLIST_ITEM
2
> {
3
>   portTickType xItemValue;
4
>   volatile struct xLIST_ITEM * pxNext; 
5
>   volatile struct xLIST_ITEM * pxPrevious;
6
>   void * pvOwner;
7
>   void * pvContainer; 
8
> };
9
> typedef struct xLIST_ITEM xListItem;    /* For some reason lint wants
10
> this as two separate definitions. */
11
>
Hat das irgendeinen Sinn?
1
struct xListItem
2
{
3
    portTickType xItemValue;
4
    volatile struct xLIST_ITEM * pxNext; 
5
    volatile struct xLIST_ITEM * pxPrevious;
6
    void * pvOwner;
7
    void * pvContainer; 
8
};

Geht doch genau so gut.

von yalu (Gast)


Lesenswert?

Am einfachsten (und saubersten) wäre natürlich, als Listenend-Item
ebenfalls ein xListItem anstatt eines xMiniListItems zu verwenden.
Dann brauchst du keine Casts und alles ist 100%ig typsicher. Ich nehme
aber an, du möchtest im Listenend-Item den Speicherplatz der Elemente
pvOwner und pvContainer einsparen.

Zunächst etwas zum logischen Zusammenhang der von dir benutzten
Datentypen:

xListItem ist sozusagen eine Erweiterung von xMiniListItem. In C++
würde man xListItem von xMiniListItem ableiten und könnte dann
problemlos (ohne Cast) einem xMiniListItem-Pointer einen
xListItem-Pointer zuweisen, nur umgekehrt nicht. Die Liste würde man
dann aber nicht aus xListItems sondern aus xMiniListItems (dem
allgemeineren Datentyp) aufbauen, d.h. alle pxNext- und
pxPrevious-Pointer wären vom Typ xMiniListItem.

Bei dir ist die Zuweisung allerdings anders herum: Du weist einem
xListItem-Pointer (pxList->xListEnd.pxNext) einen xMiniListItem-
Pointer zu (pxList->xListEnd). Das ist unschön, weil du ohne Cast und
ohne Compiler-Warnung auf

  pxList->xListEnd.pxNext->pvOwner

zugreifen könntest, was aber Müll produziert, da pxNext in diesem Fall
auf ein xMiniListItem zeigt, das das Element pvOwner gar nicht
besitzt.

Deswegen würde ich das Ganze folgendermaßen umstrukturieren:

- Bau die Liste wie oben beschrieben aus xMiniListItems auf.

- Ersetze die ersten drei Elemente von xListItem durch ein einzelnes
  Element vom Typ xMiniListItem, das ja selber diese drei Elemente
  enthält, z.B. so:

    struct xLIST_ITEM {
      xMiniListItem mini;
      void *pvOwner, *pvContainer;
    };

  Dadurch drückst du aus, dass xListItem eine Erweiterung von
  xMiniListItem ist und die strict-aliasing-Warnung bei Casts bleibt
  wahrscheinlich aus, weil der Compiler m.W. nur solche Variablen als
  nicht-aliased annimmt, deren Datentypen überhaupt nichts miteinander
  zu tun haben, sich also auch nicht gegenseitig enthalten.

- Das Listenend-Item ist nach wie vor vom Typ xMiniListItem.

- Sämtliche Listenoperationen, die auf dem Umbiegen von Pointer
  beruhen (Einfügen, Löschen usw.), werden nun auf dem einheitlichen
  Datentyp xMiniListItem ausgeführt. Dazu bedarf es keinerlei Casts.

- Beim Einfügen eines neuen xListItems (nennen wir es 'item'),
  brauchst du einen xMiniListItem-Pointer, der auf diese Struktur
  zeigt. Du erhältst ihn ebenfalls ganz ohne Cast über

    &item.mini

- Der einzige Fall, wo du noch einen Cast brauchst, ist beim Zugriff
  auf die Elemente pvOwner und pvContainer über einen xMiniListItem-
  Pointer. Dieser Zugriff birgt eine gewisse Gefahr, da nicht von
  vorneherein klar ist, ob das Item diese Elemente auch tatsächlich
  enthält. Die Unterscheidung erfolgt deiner Aussage zufolge über den
  xItemValue, d.h. vor dem Zugriff auf pvOwner und pvContainer wirst
  du eine Abfrage machen, ob es ich bei dem Item nicht um das End-Item
  handelt. Damit erreichst du, dass der Cast (immer eine potenziell
  gefährliche Aktion) genau dort gemacht wird, wo auch die Abfrage zur
  Beseitigung dieser Gefahr steht.

- Schließlich würde ich die Namen der Datenstrukturen ändern, da diese
  nicht mehr gut zum veränderten Aufbau passen, und zwar

    xListItem  in  xListItemWithContent  (oder so ähnlich, vielleicht
                                          geht's kürzer)
  und

     xMiniListItem in xListItem

  Erweiterte Datenstrukturen sollten möglichst auch erweiterte Namen
  haben. Dies ist durch obige namensänderungen geschehen.

Ich hoffe, das Ganze war wenigstens halbwegs verständlich geschrieben.
Wenn nicht (was wahrscheinlich ist ;-)), kannst ja nochmal nachfragen.


@Simon:

Ich glaube, gast programmiert in C (nicht in C++) und ist einfach zu
faul, jedesmal das 'struct' vor die Datentypen zuschreiben, deswegen
die typedefs.

von gast (Gast)


Lesenswert?

Nun werde ich das Beispiel, ist ja ein Auszug aus dem FreeRTOS - also 
nicht von mir Programmiert - einmal Aufzeichnen, was da genau gemacht 
wird, und mit den von dir Erhaltenen Informationen verstehen versuchen, 
denn genau da scheitert es bei mir noch etwas.

Aufzuzeigen, wie soetwas in C++ gelöst wird, war schon sehr hilfreich, 
normalerweise Programmiere ich nämlich nur SPS'n in Structured Text, 
welches sehr Objektorientiert ist.

Hatte zwar C in der Schule, aber wenn man selten etwas damit macht, dann 
scheitert es leider schnell am Verständnis.

Werde mich wieder melden, wenn ich es durchblickt - oder auch nicht 
durchblickt - habe.

mfg. Gast

von gast (Gast)


Lesenswert?

Natürlich fehlt wiedermal eine Edit-Funktion.

Ich vermute einmal, dass es einen guten Grund hat, wieso das so gelöst 
wurde -> Speicherverbrauch?

mfg. Gast

von Uhu U. (uhu)


Lesenswert?

gast wrote:
> Natürlich fehlt wiedermal eine Edit-Funktion.
>
> Ich vermute einmal, dass es einen guten Grund hat, wieso das so gelöst
> wurde -> Speicherverbrauch?

Nein, das liegt an dir. Wenn du dich registrierst und ordentlich 
einloggt, dann hast du die Editfunktion.

Strafe muß sein...

von gast (Gast)


Lesenswert?

Ich weiß eh, dass es daran liegt, das ich nicht registriert bin.

Würde es nicht reichen, den Cast zu ändern?

Von
1
pxList->xListEnd.pxNext = ( xListItem * ) &( pxList->xListEnd );

auf
1
pxList->xListEnd.pxNext = ( xListMiniItem * ) &( pxList->xListEnd );

oder habe ich da jetzt einen Denkfehler?

Ich glaube ich muss zugeben, dass ich da noch immer nicht ganz so 
durchblicke, wie ich es eigentlich sollte.

-> 10 Minuten später:

So nochmals mit aufzeichnen, ja ich habe einen Denkfehler, denn mit 
pxList->xListEnd.pxNext greife ich ja auf ein Element der Struktur 
xListItem zu und mit pxList->xListEnd auf ein Element der Struktur 
xMiniListItem, wenn ich das richtig verstehe.

Die vorgeschlagene Änderung von dir xMiniListItem in xListItem zu 
integrieren, halte ich für ausgezeichnet, werde ich dann mal durch den 
Compiler jagen, ob er da meckert und zudem muss ich noch schauen, wie es 
sich mit dem Rest der Source verhält.

Die Programmierer eines RTOS sind ja auch nicht fehlerfrei ^^

mfg. Gast

von gast (Gast)


Lesenswert?

Hm, was würde der Compiler denn machen, wenn ich einfach auf ( void * ) 
caste?

Sollte in C nicht eientlich der Typ des Pointers egal sein?

mfg. Gast

von yalu (Gast)


Lesenswert?

> Die Programmierer eines RTOS sind ja auch nicht fehlerfrei ^^

Das können sie auch gar nicht. Was nach heutigem C Standard noch
richtig ist, kann morgen schon ein Fehler sein ;-)

Naja, ganz so schlimm ist's dann doch nicht. Aber gerade diese
Aliasing-Regeln, die u.a. besagen, dass man auf ein Objekt eines
bestimmten Datentyps nicht über einen Pointer auf einen anderen
Datentyp zugreifen darf (es gibt aber Ausnahmen), gab es nicht immer.
Ich glaube sogar, sie kamen erst mit ISO C99. Führer waren solche
Konstrukte, wie du sie gezeigt hast, ganz normal und nicht einmal
schlechter Programmierstil. Deswegen kann man solche "Ungenauigkeiten"
(um den hässlichen Begriff "Fehler" zu vermeiden) nicht unbedingt den
Programmieren anlasten.

Auch heute wissen viele Programmierer noch nichts von diesen Regeln,
die dem Compiler bspw. klare Anweisungen darüber geben, wann und für
wie lange Variablenwerte in Registern gehalten werden können, um
schnelleren Code zu erzeugen. GCC kennt und benutzt diese Regeln seit
Version 3.4.1, also erst seit ca. 3 Jahren. Ich vermute, dass sie in
vielen anderen Compilern bis heute noch nicht implementiert sind.

Mir ist aber nicht ganz klar, was der Grund für deine Überlegungen
ist, wobei Neugier und Interesse an solchen Dingen natürlich immer
anzuerkennen ist. Ich bin anfänglich davon ausgegangen, dass du das
FreeRTOS ändern oder erweiteren möchtest (oder dass du am Ende
vielleicht einer der Entwickler bist :-)). Jetzt habe ich eher den
Eindruck, dass du den FreeRTOS-Code einfach nur verstehen möchtest.
Wenn du FreeRTOS nur benutzen möchtest, ist der pragmatischste Weg
sicher, -fno-strict-aliasing zu benutzen.

> Hm, was würde der Compiler denn machen, wenn ich einfach auf ( void * )
> caste?

Das verhindert wahrscheinlich die Ausgabe der Warnung, aber du
verbaust dem Compiler damit evtl. Optimierungsmöglichkeiten.

> Sollte in C nicht eientlich der Typ des Pointers egal sein?

Datentypen (auch bei Pointern) ermöglichen dem Compiler, die Absichten
des Programmierers besser zu "verstehen" und dadurch den Coder besser
zu optimieren oder ggf. Warn- und Fehlermeldungen auszugeben, wenn an
diesen Absichten etwas widersprüchlich erscheint und so den
Programmierer bei der Fehlersuche zu unterstützen.

Guter Stil es es deswegen, die Datentypen immer so treffend wie
möglich zu wählen, mit Casts zu sparsam umzugehen und Void-Pointer nur
in Ausnahmefällen zu verwenden.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

gast wrote:

> Hm, was würde der Compiler denn machen, wenn ich einfach auf ( void * )
> caste?

Du würdest das Symptom beheben statt der Ursache.  Laut C-Standard
darfst du einen void * nur als Vehikel benutzen, um ihn hinterher
wieder auf den gleichen Typ zu casten, der er vorher mal war.

Kann also gut sein, dass du damit die Compilerwarnung beseitigen
würdest -- aber nur, indem du GCC damit austrickst, weil er sich
unmöglich intern alle Typen merken kann, die mal in einen void *
konvertiert worden sind.

von horst (Gast)


Lesenswert?

Wäre nicht auch ein
1
pxList->xListEnd.pxNext = (xListItem) ( ( xListMiniItem * ) &( pxList->xListEnd ) );

legitim?

grüße, Horst

von yalu (Gast)


Lesenswert?

> Wäre nicht auch ein
>
> pxList->xListEnd.pxNext = (xListItem) ( ( xListMiniItem * )
> &( pxList->xListEnd ) );
>
> legitim?

Nein. Der Cast (xListMiniItem *) ist überflüssig, da der Ausdruck
rechts davon ja schon ein Pointer auf auf xListMiniItem ist. Der Cast
(xListItem) würde einen Pointer in eine Struktur casten, das geht
nicht. Oder hast du nur den Stern vergessen und es sollte (xListItem
*) heißen? Dann bleibt aber, wenn man den überflüssigen Cast weglässt,
gasts ursprünglicher Ausdruck übrig, der zu besagter Warnung führt.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.