Forum: Compiler & IDEs GCC warning cast expression as lvalue


von Sebastian Schildt (Gast)


Lesenswert?

Hi!

Ich habe mir gerade das aktuelle WinAVR Paket geholt (GCC ist avr-gcc
(GCC) 3.4.3) und habe jetzt beim kompillieren eine Warnung die vorher
(glaube ich) nicht da war.

sysTimer.c:26: warning: use of cast expressions as lvalues is
deprecated
sysTimer.c:26: warning: use of cast expressions as lvalues is
deprecated
sysTimer.c:27: warning: use of cast expressions as lvalues is
deprecated
sysTimer.c:27: warning: use of cast expressions as lvalues is
deprecated

Der zugehörige Code

void (*fcall)(void);
(uint16_t)fcall  = timers[i].destination << 8; //high byte <- 26
(uint16_t)fcall |= timers[i].signalId;         //low  byte <- 27
fcall(); //Call it!

Was gefällt dem GCC an meinem schönem gecaste nicht :D ? und wie ginge
es besser? destination und signalID sind uint8_t.

von Rufus T. Firefly (Gast)


Lesenswert?

Das gefällt dem Compiler zurecht nicht:

   (uint16_t)fcall  = timers[i].destination << 8;

Wie die Fehlermeldung schon sagt: Du castest einen lvalue.

Was verleitet Dich zur Annahme, Du könntest eine Funktionsadresse auf
diese Art und Weise berechnen? Die wird doch sicherlich nicht in zwei
Bytes verpackt in .destination und .signalId untergebracht sein ...

Die Warnung kannst Du durch eine andere ersetzen, wenn Du stattdessen

   fcall  = (uint16_t) (timers[i].destination << 8);

schreibst. Wäre fcall vom Typ uint16_t, gäbe es auch keine weitere
Warnung, die aber gibt es nach wie vor, weil fcall vom Typ
Funktionspointer ist. Und Variablen dieses Typs kannst Du auf diese Art
und Weise keine Werte zuweisen.

Was exakt bezweckst Du?

von Sebastian Schildt (Gast)


Lesenswert?

Was ich damit tue ist in der Tat ein dreckiger Trick :) Es habe ein
Laufzeitsystem welches Timer unterstützt. Wenn ein Timer abgelaufen
ist, wird das Signal signalId and die Komponente mit der ID
"destination" übergeben. Zu diesem Zweck wird das normale Messaging
System des OS genutzt, d.h. ein entsprechendes Signal wird in die Queue
gestellt. Was ich hier tue ist eine Abkürzung: Ich unterstütze
"fastcalls", d.h. wenn ein Timer abgelaufen ist dann kann auch unter
Umgehung des Messaging Systems direkt eine Funktion aufgerufen werden
(angenommen man pollt irgendeine Hardware und das Timing ist kritisch).
Da hierfür die selbe Timer Struktur "missbraucht" wird, wird lediglich
ein Flag gestzt das sagt "Achtung! Messaging system umgehen" und das
RTS weiß, dass die Addresse der Funktion in signalID und Destination
aufgespaltet zu finden ist.
Abgesehen davon, dass man über das Design sicher streiten kann, funzt
es wunderprächtig :) Nur die Warnung stört halt ein wenig :)

von Rufus T. Firefly (Gast)


Lesenswert?

Wenn ich Dich richtig verstehe, steht in Deiner Nachrichtenstruktur also
tatsächlich die anzuspringende Adresse.

Ich fragte so skeptisch, weil hier oft Leute die seltsamsten Dinge
anstellen, obwohl sie doch ganz banale Sachen erreichen wollen.
Du stellst insofern eine beruhigende Ausnahme dar.

Die Warnung wirst Du vermutlich los, wenn Du

fcall = (void *) (((uint16_t) timers[i].destination) << 8) |
((uint16_t) timers[i].signalId));

schreibst.

Also zunächst die beiden Bytes nach uint16_t castest, dann die
Bitarithmetik damit anstellst und das Resultat nach void* castest.

Will man das ganze warnungsfrei mit zwei getrennten
Zuweisungsoperationen veranstalten, wie Du es versuchst, wird das ganze
nicht wirklich übersichtlicher; ich bin mir jetzt nicht sicher, ob der
Compiler ein |= auf einem Pointer überhaupt legal findet. Also müsste
man für das |= den Pointer wieder in uint16_t casten und das Resultat
... also nee.

Abgesehen von der etwas sehr kriminellen Verpackung des
Funktionspointers in der Struktur sehe ich noch nicht mal sonderlich
anrüchiges in Deinem Vorhaben. C ist halt was für Leute, die wissen,
was sie tun :-)

von Jörg Wunsch (Gast)


Lesenswert?

Eine union wäre der richtige Weg, nicht wahr?

von Rufus T. Firefly (Gast)


Lesenswert?

Das (die union) setzt voraus, daß Sebastian den Sourcecode seines
Laufzeitsystems anpasst, immerhin müssen dann alle Zugriffe auf die
entsprechenden Felder der Struktur angepasst werden.
Aus Speicherplatzgründen verbietet sich vermutlich auch das Einfügen
eines zusätzlichen void-Pointers in der Struktur - wenn dem nicht so
sein sollte, wäre das ein Weg, bei dem das Laufzeitsystem nicht
geändert werden müsste.

von Matthias (Gast)


Lesenswert?

Hi

er hat doch die Definition seiner "timers"-Struktur. Dann sollte doch
sowas gehen:

union{
    typeof(timers);
    hisownstructwithfuncpointer;
};

Matthias

von Jörg Wunsch (Gast)


Lesenswert?

Die Kompatibilität zum ursprünglichen Quellcode kann man dann
weitgehend sogar durch ein #define wieder herstellen.

von Rufus T. Firefly (Gast)


Lesenswert?

@Matthias:
Du hast recht:

Angenommen, die Struktur wäre ursprünglich so deklariert:

  struct Message
  {
    uint8_t destination;
    uint8_t signalId;

    uint8_t irgendwasanderes;
    uint16_t nochwas;
  };

Dann ließe sich das in der Tat so ersetzen:

  struct NeueMessage
  {
    union
    {
      struct
      {
        uint8_t destination;
        uint8_t signalId;
      };
      void* p;
    };
    uint8_t irgendwasanderes;
    uint16_t nochwas;
  };

Und das, ohne sonst erweiterten Tippaufwand zu erfordern.

Jaaa, ich muss zugeben, daß ich nicht jeden Tag mit unions jongliere.

von Chris (Gast)


Lesenswert?

@Rufus T. Firefly:
(eins vorweg, ich nehme mal an, dass das folgende nicht nur für C++,
sondern auch für C gilt.)

Du castest das Ergebnis dieses Bitshifts auf void*.
Ein void* ist allerdings ein beliebiger _Daten_-Zeiger. Funktionszeiger
können (und Methodenzeiger sind es in C++ auch) größer sein als void*.
Daher kann ein void* ohne harten cast nicht in einen Funktionszeiger
konvertiert werden, da es etwas völlig inkompatibles ist.

Eine union ist eindeutig der bessere Weg, um wenigstens standardkonform
zu bleiben. Allerdings sollte die union aus oben beschriebenem Grund
einen Funktionszeiger und kein void* als "Neben-Element" enthalten.
Falls der Funktionszeiger wirklich mal größer sein sollte als 16 Bit,
z.B. weil du den Code woanders benutzen möchtest, funktionert es mit
einer union immer noch (die ist nämlich so groß wie ihr größtes
Element). Mit einem void* hättest du dann u.U. arge Probleme.

von Rufus T. Firefly (Gast)


Lesenswert?

Ich gestehe hier gewisse vereinfachende Annahmen über die Hardware der
verwendeten Maschine zu machen; der Fall, daß sizeof (void*) != sizeof
(funktion*) ist, ist IMHO verhältnismäßig selten*.
Bei der ursprünglichen Aufgabenstellung ist es jedenfalls definitiv
dasselbe.

Mein etwas brachiales Typecast-Konstrukt hat nie an sich den Anspruch
der Portabilität gestellt (siehe Aufgabenstellung) und hat sich mit der
Unionslösung ja auch überlebt.

Wenn man die Unionslösung verwendet, dann kann man selbstverständlich
auch mit einem "richtigen" Funktionspointer arbeiten; ich schrieb
unter anderem aus Tippfaulheit das kürzere void* hin.

Was exakt magst Du mit "Methodenzeiger in C++" meinen?

BTW: Du bist eindeutig ein anderer Chris als der Chris, der mir hier in
letzterer Zeit unangenehm aufgefallen** ist; im Gegensatz zu jenem sind
Deine Beiträge fundiert, sachlich und insgesamt erfreulich.

*) AVR mit mehr als 64 KWorten Flash sind so ad hoc die einzigen, die
mir da einfallen.
**) Den hier meine ich
http://www.mikrocontroller.net/forum/read-2-162675.html#162675

von Chris (Gast)


Lesenswert?

> Was exakt magst Du mit "Methodenzeiger in C++" meinen?

Sowas:

class foo {
public: void bar();
};
typedef void (foo::*MethodenZeiger)();
int main() {
foo f;
MethodenZeiger p = &foo::bar;
f.*p(); // entspricht f.bar();
}


Methodenzeiger haben in C++ eine etwas eigenwillige Syntax, können aber
ganz praktisch sein. In diesem Beispiel kann (muss aber nicht) der
Zeiger p genauso groß sein wie ein normaler Funktionszeiger. Sobald
allerdings Mehrfachvererbung hinzukommt, muss der Methodenzeiger
mindestens ein zusätzliches Feld neben der Funktions-Adresse enthalten,
nämlich den Offset des erwarteten Objekts zur Basisklasse.
Beispiel:
class foo { public: void bar(); };
class bla { public: void bar2(); };
class derived : public foo, public bla {
public: void bar3();
};
// Ein Zeiger auf eine Methode von derived
typedef void (derived::*BoeserZeiger)();

void blub()
{
derived d;
BoeserZeiger p1 = &foo::bar;
BoeserZeiger p2 = &bar::bar2;
BoeserZeiger p3 = &derived::bar3;
d.*p1();
d.*p2();
d.*p3();
}

Jeder der drei Zeiger zeigt auf eine andere Methode, soweit logisch.
Bei dem Aufruf einer Methode wird unsichtbar das Objekt d in Form des
this-Zeigers übergeben, wie bei jedem nichtstatischen Methodenaufruf in
C++.
Nun gibt es aber ein gewaltiges Problem: foo::bar() erwartet einen
this-Zeiger, der auf ein foo-Objekt zeigt, bar2 einen, der auf ein
bla-Objekt zeigt, und bar3 schließlich einen this-Zeiger auf ein
derived-Objekt. Bei Einfach-Vererbung (man denke sich bla komplett weg)
ist das kein Problem, weil der Compiler in diesem Fall derived im RAM
einfach hinter foo anordnen würde, sodass jedes derived-objekt ohne
Konvertierung auch ein foo-Objekt ist, indem man einfach die hinteren
(derived-)Bytes ignoriert.

Bei Mehrfachvererbung funktioniert dieser Trick leider nicht mehr. Hier
muss der Methodenzeiger neben der Funktionsadresse also ein weiteres
Feld enthalten, das dem Compiler ermöglicht, den this-Zeiger korrekt
anzupassen.


Gute Infos gibt zum Beispiel diese Seite:
http://www.codeproject.com/cpp/FastDelegate.asp
Die Seite ist gerade nicht erreichbar, daher noch der Link zur
Google-Cache-Version:
http://216.239.59.104/search?q=cache:OiBh617rXOQJ:www.codeproject.com/cpp/FastDelegate.asp&hl=de

Mangels Methoden gibt es sowas (zum Glück der Compilerbauer, s.u.) in C
nicht.


> BTW: Du bist eindeutig ein anderer Chris als der Chris, der mir hier
> in letzterer Zeit unangenehm aufgefallen** ist; im Gegensatz zu
jenem
> sind Deine Beiträge fundiert, sachlich und insgesamt erfreulich.
Ich fühle mich geehrt. :-)

Vielleicht sollte ich mir doch mal einen anderen Nickname zulegen, es
gibt einfach zu viele Chris' hier.



p.s.:
Ich erhebe keinen Anspruch auf Fehlerfreiheit und Vollständigkeit der
Erklärung (ich glaube ich habe es selbst noch nicht hundert prozent
verstanden). Inbesondere virtuelle Vererbung (ein absolut
C++-spezifisches und unübliches Konstrukt) und templates habe ich gar
nicht erwähnt. Dadurch werden Methodenzeiger nochmal ein gutes Stück
komplizierter.

Nur um die Verhältnisse zu zeigen: Während beim MS Visual C++ Compiler
ein normaler Funktionszeiger 4 Bytes groß ist, ist ein Methodenzeiger
im worst case sagenhafte 16 Bytes groß.
Methodenzeiger sind wirklich ein enorm komplexes Thema, vor allem für
die armen Leute, die dieses Feature in einen Compiler einbauen dürfen.

von Rufus T. Firefly (Gast)


Lesenswert?

Vielen Dank für die gute Erklärung, die auf jeden Fall auch ein guter
Anhaltspunkt für eigene Nachforschungen ist.
Kurzform wäre also so etwas wie Funktionspointer mit this-Pointer.
Ich bin froh, kein Compilerbauer zu sein; mein Ausflug in diese Gefilde
war mal der Versuch, einen Cross-Compiler für Small-C (das ist ein
gegenüber dem K&R-C nochmal reduzierter Dialekt) um einige
Sprachkonstrukte zu erweitern und gleichzeitig den Codegenerator auf
(m)einen Prozessor (6809) anzupassen.
Resultat war eine funktionierende Sourcecoderuine. Ich hab' dabei aber
viel über C gelernt; war also eine lohnende Beschäftigung. Lang ist's
her.
Von C++ nutze ich ein von mir verstandenes Subset.

Betr. Chris: Während meines Studiums hatte ich aus irgendwelchen
Gründen mit derartig vielen Andreas' zu tun, daß ich irgendwann damit
anfing, einfach jeden so anzureden - es stimmte ja meistens ...

von Chris (Gast)


Lesenswert?

> Kurzform wäre also so etwas wie Funktionspointer mit this-Pointer.
Nicht direkt; ein Methodenzeiger enthält keinen this-Pointer, weil er
ja auf jedes Objekt aufgerufen werden kann. Ohne Objekt ist ein
Methodenzeiger nutzlos.
.* und ->* sind übrigens eigene Operatoren in C++, keine
Zusammensetzungen aus "." bzw. "->" und dem
Dereferenzierungsoperator "*".

In dem ersten Beispiel oben könnte man ohne weiteres ein zweites
foo-Objekt erzeugen und den Methodenzeiger drauf anwenden. this wäre
dann jeweils das aktuelle Objekt.

von Sebastian Schildt (Gast)


Lesenswert?

Hallo,

danke für die Antworten. Was die Vorschläge angeht:

union:
Ja, da war was. Ich habe auch mal 2 sek drücber nachgedacht, es dann
aber nicht gemacht, weil ich in anderem Code noch NIE gesehen habe das
sowas verwendet wird. (ok, doofe Begründung). Benutzt hier jemand
"regelmäßig" unions? Und kann man davon ausgehen da sie von allem
Wald- und Wisen Compilern untestützt werden? Ist zwar ANSI, aber da ich
halt vermute dass es selten verwendet wird, weiß man ja nicht so
genau...

programme  ändern:
Programme müssen nicht angepasst werden, wenn ich z.B. die Struktur des
structs ändere, da der normale "user" beim Programmieren einer
Komponente dies nicht mitbekommt. Timer werden über
int8_t createFastTimer(uint16_t ticks , uintptr_t *fptr, uint8_t
loop);
oder
int8_t createTimer(uint16_t ticks, uint8_t dest, uint8_t sigid, uint8_t
loop);
erzeugt, d.h. der Programmierer bekommt nicht mit wie das intern
verwaltet wird.


MfG

Sebastian

von Chris (Gast)


Lesenswert?

Soweit ich das weiß:
unions sind für Compilerbauer ziemlich einfach zu implementieren. Kein
Vergleich zu Methodenzeigern oder Templates (die ich oben erwähnt
habe).
unions werden mit Sicherheit von jedem C-Compiler unterstützt.

von Matthias (Gast)


Lesenswert?

Hi

Die C-Compiler die mir bisher über den Weg gelaufen sind (GCC für x86,
ARM, SH4, AVR; VC6 für x86; Keil für C166; SDCC für 8051) funktionieren
eigentlich ganz gut was den reinen Sprachumfang angeht. Die
Standardbibliothek ist zwar nicht immer ganz komplett was aber daran
liegen dürfte das ich eher in Richtung µC entwickle. Da brauch ich
üblicherweise kein fprintf().

Bei C++ siehts dann aber schon ganz anders aus. Trotz meiner nicht bis
ins letzte Detail vorhandenen Kenntnisse bin ich insbesondere beim
VC++6 hin und wieder mal auf Dinge gestoßen die nicht funktionieren.
Insbesondere in Bereich von Templates versagt dieser. Aber auch ein
simples "static const double TWO_PI = 3.1415*2;" in einer Klasse mag
dieser Compiler nicht.

Matthias

von Chris (Gast)


Lesenswert?

@Matthias:
Der Microsoft C++ Compiler 7.1 (noch nicht 7.0) ist dagegen ein richtig
guter Compiler, der einen Großteil des Sprachstandards unterstützt. Der
hat auch bedeutend weniger Probleme mit templates als der 6er. :-)

von Matthias (Gast)


Lesenswert?

Hi

hab ich mir auch schon sagen lassen. Ich bin auch drauf und dran mir
das VS .net als SSL Version zu kaufen. Wollte aber evtl. die nächste
Version mit Unterstützung von AMD64 abwarten.

Matthias

von Rufus T. Firefly (Gast)


Lesenswert?

@Matthias:
Das Compiler-Backend (also die Kommandozeilentools etc.) von VC++7.1
gibt es kostenlos von MS; wenn es Dir nicht auf die VS2003-IDE ankommt,
kannst Du auch so mit dem Compiler arbeiten.

http://www.mikrocontroller.net/forum/read-8-127053.html#132982

von Matthias (Gast)


Lesenswert?

Hi

ich weiß. Die IDE ist es aber eigentlich die es mir etwas angetan hat.
Zusammen mit Visual Assist ist das Teil genial um durch größere
Projekte mit fremden Sourcecode zu navigieren.

Wenn ich nur einen halbwegs ordentlichen Compiler brauche nehm ich den
GCC.

Matthias

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.