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.
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?
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 :)
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 :-)
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.
Hi er hat doch die Definition seiner "timers"-Struktur. Dann sollte doch sowas gehen: union{ typeof(timers); hisownstructwithfuncpointer; }; Matthias
Die Kompatibilität zum ursprünglichen Quellcode kann man dann weitgehend sogar durch ein #define wieder herstellen.
@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.
@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.
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
> 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.
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 ...
> 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.
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
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.
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
@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. :-)
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
@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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.