www.mikrocontroller.net

Forum: Compiler & IDEs GCC warning cast expression as lvalue


Autor: Sebastian Schildt (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Rufus T. Firefly (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Sebastian Schildt (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 :)

Autor: Rufus T. Firefly (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 :-)

Autor: Jörg Wunsch (Gast)
Datum:

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

Autor: Rufus T. Firefly (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

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

union{
    typeof(timers);
    hisownstructwithfuncpointer;
};

Matthias

Autor: Jörg Wunsch (Gast)
Datum:

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

Autor: Rufus T. Firefly (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Rufus T. Firefly (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: Chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:...

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.

Autor: Rufus T. Firefly (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 ...

Autor: Chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Sebastian Schildt (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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. :-)

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Rufus T. Firefly (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.