|
Antwort |
Registriert seit: 18. Feb 2007 Ort: Schweinfurt 170 Beiträge Delphi XE Professional |
#1
Hallo Leute,
ich bräuchte mal Eure Hilfe zu einem absolut einfachen Problem aus der C++-Welt. Irgendwie stehe ich so dermaßen auf dem Schlauch, dass mir auch eine Suche im Internet keine Erleuchtung brachte. Vorab: Das Ganze Programm soll auf einem Arduino laufen, daher ist die Sparache nicht Delphi, sondern C++. Auch sind geringer Speicher- & Ram Verbrauch relativ wichtig! Da das gesamte Programm (für meine Verhältnisse) recht komplex ist, habe ich mich für einen objektorientierten Ansatz entschieden. Gegeben sei eine Klasse / Library, die ein von mir entwickelten "Shield" (Arduino-Slang für angeschlossene Hardware) steuern soll. An diesem Shield einer einfachen Bewässerungsteuerung ist auf jeden Fall (unter Anderem) ein Auslassventil angeschlossen, ein Einlassventil ist optional anschließbar, jedoch nicht zwingend nötig. Wenn das Einlassventil nicht angeschlossen ist, will ich (da Speicherplatz auf dem Arduino rar ist) die die Valve-Instanz inletValve gar nicht erst erzeugen. Doch wie teste ich nun im weiteren Verlauf des Programmes, ob ich zu Beginn inletValve erzeugt habe oder nicht? Ob ich somit darauf zugreifen darf oder nicht? In Delphi würde ich das Problem so lösen:
Delphi-Quellcode:
Doch leider ist Delphi nicht die Zielsprache In C++ schwebt es mir vor, das Ganze so umzusetzen:
type TFluidWorks = class()
private FInletValve : TValve; FOutletValve : TValve; public constructor Create(_hasInletValve : Boolean); procedure switchOn; end; TFluidWorks.Create(_hasInletValve : Boolean); begin FOutletValve := TValve.Create(...); if _hasInletValve then FInletValve := TValve.Create(...); end; procedure TFluidWorks.switchOn; begin FOutletValve.switchOn; if Assigned(FInletValve) then //<------test, ob FInletValve "gültig" FInletValve.switchOn; end;
Code:
Ich bin mir nun an der markierten Stelle unsicher: Wie teste ich, ob die Referenz auf inletValve "gültig" ist, genauer gesagt: ob also eine Valve-Instanz namens inletValve existiert?
class FluidWorks {
public: FluidWorks(bool hasInletValve); void switchOn(); private: Valve inletValve; Valve outletValve; }; FluidWorks::FluidWorks(bool hasInletValve){ outletValve = Valve(...); if (hasInletValve){ inletValve = Valve(...); } } void FluidWorks::switchOn(){ outletValve.switchOn(); if (&inletValve != NULL){ //<--------- hier bin ich mir unsicher inletValve.switchOn(); } } Die Funktionalität kann ich mangels Arduino-Board nicht testen, alles, was ich derzeit machen kann, ist zu sehen, ob der Compiler durchläuft... Nur leider ist ein "fehlerfreies" Kompilat nicht gleichbedeutend mit einem "funktionierenden" Programm... Das Thema https://www.delphipraxis.net/151591-[c-]-assigned.html kenne ich, aber mein Verständnisproblem scheint / grundlegender tiefer zu sein... (inletValve != NULL) (also ohne den &-Operator) quittiert der Compiler (aus der Arudino-IDE) mit [pre] error: no match for 'operator!=' (operand types are 'Valve' and 'int') [/pre] (&inletValve != NULL) läuft zumindest durch, aber könnt Ihr mir sagen, ob der Code das tut, was ich von ihm will? Für eine kurze Antwort bzw. Eure Einschätzung wäre ich dankbar.
Tobias
Bitte nicht hauen , ich weiß es nicht besser |
Zitat |
silver-moon-2000 |
Öffentliches Profil ansehen |
Mehr Beiträge von silver-moon-2000 finden |
Registriert seit: 6. Feb 2008 838 Beiträge |
#2
Ist dir klar, das Delphi ClassObjekte nur "Zeiger" sind und nur Delphi RecordObjekte dem von dir hier gezeigtem C++ Style der Adruino direkt vergleichbar wären?
daher in deinem aktuellem Code: "if(&inletValve != NULL)" ist immer Wahr, weil du dein "inletValve" als statisches Speicherobjekt angelegt hast. "&inletValve" ist die Adresse deines objekts im Speicher und die ist immer ungleich NULL, Speicher spartst du so nicht, weil der ja bei dir so immer fix angelegt. Soweit so gut und einfach in "echtem" C++... einfach als Pointer anlegen und mit new erzeugen... anlegen Valve *pinletValve; bei Bedarf erzeugen if(hasInValve) pinletValve = new Valve(...); else pinletValve=NULL; nur wenn vorhanden nutzen if(pinletValve) pinletValve->switchOn(); "Assigned" braucht es in c/c++ nicht, man schreibt einfach if(p) Adruino C++ kenne und habe ich nicht, daher keine Garantie ob dynamische "HW" Objekte mit Adruino C++ möglich sind. |
Zitat |
Registriert seit: 6. Mär 2013 6.158 Beiträge Delphi 10 Seattle Enterprise |
#3
Kennt dein Compiler nullptr
?
|
Zitat |
Der schöne Günther |
Öffentliches Profil ansehen |
Mehr Beiträge von Der schöne Günther finden |
Registriert seit: 6. Feb 2008 838 Beiträge |
#4
Kennt dein Compiler nullptr
?
So Feinheiten wie "nullptr" aus "C++V11" sind da nicht wirklich wichtig, zumal man in C/C++ bei Bedarf sehr einfach "auf alles" casten kann. Adruino ist ne super Basis für den Embedded Einstieg. Das OO Konzept HW-Shield's per einfacher statischer Definition(C++ ClassStruct ala "Delphi Record") mit sofort verfügbarer und einfach nutzbarer Funktionalität "an alle" zu verteilen hat schon was. Allerdings sollten wenigsten die, welche die "Shield's" programmieren mit den Grundregeln der HW nahen nunmal Address & Pointer basierten C/C++ Programmierung vertraut sein. Leider ist dies nicht immer der Fall. Ich kaufe auch gerne billigste Test-HW als "Adruino-Shield" und mache mittlerweile als erstes aus dem Adruino C++-Code wieder einen simple C-Code, welcher die Funktion ganz ohne OO im C-File hat und alles was Hardware spezifisch ist wird im H-File via Defines "auf externes gemappt" oder wenn möglich hier direkt zugeordnet. Flexible HW Nutzung ist eine nicht unübliche Herausforderung, nur frage ich mich, ob es hier unbedingt der Objektorientierung Bedarf!?... Da gibt es seit Jahrzehnten bewährt das #IFDEF HAS_XY... so wird auch wirklich nur das übersetzt und gelinkt was real vorhanden&benötigt. Das spart dann nicht nur den RAM, sondern auch den Programmspeicher als ROM!!! Ich denke der TE sollte sich bei wirklichem Interesse an guter Speicheroptimierung mehr in dieser Richtung orientieren. In C/C++ geht quasi stets "alles", egal ob jeweils sinnvoll oder nicht |
Zitat |
Registriert seit: 15. Mär 2007 4.093 Beiträge Delphi 12 Athens |
#5
Ich hab die aktuellen Compilerentwicklungen nicht mehr so verfolgt,
aber könnte der C++ Compiler mittlerweile besser optimieren als ein C Compiler ? Ich denke eher nicht, aber vielleicht gibt es ja Benchmarks zu dem Thema die das Bestätigen könnten. In meinen Projekten habe bei 8-Bit immer grundsätzlich C benutzt (oder C++ ohne Klassen, was fast das Gleiche ist), denn C++ ist etwas für größere Betriebssysteme. Die Vorteile von C++ kommen bei Stack/Heap Memoryverwaltung von vielen Klassen, Objekten und Threads. In C für 8-Bit reicht mir meistens eine optimierte main loop, welche die Events abarbeitet, ganz ohne den RTOS Overhead Wenn man das richtg anlegt ist es sehr effizient, sowohl vom Speicher, vom Timing als auch von der Verarbeitung her. Es ist auch hochportabel, und so habe ich damit diverse Controllerprojekte in C++Builder simuliert, und konnte mir einen digital Mockup für die Entwicklung bauen. Auch die Kunden sind über einen Simulator Ihres Projektes recht erfreut, z.B. für Schulungen, Messen, etc. Rollo |
Zitat |
Registriert seit: 18. Feb 2007 Ort: Schweinfurt 170 Beiträge Delphi XE Professional |
#6
Hallo,
vorab: Danke für Eure Antworten und die genommene Zeit. Bitte entschuldigt meine späte Reaktion. Ich habe die Zeit genutzt, Eure Antworten zu verdauen und mich noch etwas weiter im Netz zu informieren... -> das Problem ist, er hat keinen Computer-Compiler sondern einen 8Bit Microcontroler mit einstellig !KB!-RAM und 2stellig !KB! (Flash)Rom...
Flexible HW Nutzung ist eine nicht unübliche Herausforderung, nur frage ich mich, ob es hier unbedingt der Objektorientierung Bedarf!?...
Da gibt es seit Jahrzehnten bewährt das #IFDEF HAS_XY... so wird auch wirklich nur das übersetzt und gelinkt was real vorhanden&benötigt. Das spart dann nicht nur den RAM, sondern auch den Programmspeicher als ROM!!! [...] [...]In C für 8-Bit reicht mir meistens eine optimierte main loop, welche die Events abarbeitet, ganz ohne den RTOS Overhead [...]
Das Programm entstand ursprünglich als rein prozeduraler Ansatz und ist (sowohl software- als auch hardwaretechnisch) gewachsen, bis ich vollkommen den Überblick verloren und einfach nur irgendwie drangefrickelt hatte. Das soll nun deutlich besser werden, daher schreibe ich das Programm komplett neu. Hardwaretechnisch bin ich derzeit bei 5 Shields, die aufeinander "gestackt" werden und softwaremäßig bei >2500 LoC und 11 Klassen, obwohl noch nicht alles programmiert ist. Ja, natürlich ließe sich auch rein prozeduraler Code besser und übersichtlicher strukturieren, als ich das "damals" gemacht habe, aber ich persönlich bin der Meinung, dass bei diesem Funktionsumfang die Übersichtlichkeit, Erweiterbarkeit und Wiederverwendbarkeit bei objektorientiertem Code einfach besser ist. Mal sehen, ob am Schluss alles in den Flash passt oder ob ich mir mit dem zusätzlichen "Overhead" in den Fuß geschossen habe und doch zu prozeduralem Code wechseln muss. Zum Thema Compilerdirektive Die Compilerdirektive #ifdef kenne ich, doch nützt die mir hier leider nicht. Denn was per Direktive nicht einkompiliert wurde, ist ja im Code nicht vorhanden, Änderungen erfordern ein Neukompilieren... Es kommt jedoch recht häufig vor, dass ich Hardware vom Arduino abziehe oder anstecke, ich will nicht jedes Mal den Code neu komplilieren müssen. Da der Arduino sowieso über den PC gesteuert werden soll, will ich den "Funktionsumfang" des Arduino in meinem PC-Programm einstellen können. Um beim obigen Beispiel zu bleiben: Wenn ich ein Einlassventil anschließe, will ich einfach im PC-Programm einen Haken "Einlassventil vorhanden" setzen. Der Arduino soll dann automatisch eine Instanz von Valve namens inletValve erzeugen. Oder eben keine Instanz erzeugen, wenn kein Einlassventil angeschlossen ist. Das geht natürlich nur, wenn das inletValve nicht per #ifdef "komplett auskommentiert" ist. Weil aber, wie gesagt, vor allem der SRAM ziemlich begrenzt ist, will ich versuchen, nur die Objekte zu erzeugen, die ich brauche, bzw. nicht mehr gebrauchte zügig wieder freizugeben. So ist es zuimdest derzeit angedacht. Wenn ich feststellen sollte, dass der Speicher vorne und hinten nicht reicht, wird mir nichts anderes übrigbleiben, als nur das unmittelbar nötige per Compilerdirektive einzubinden und dann, wenn sich die Hardware-Zusammensetzung ändert, eben auch den Code neu zu kompilieren... Allerdings sollten wenigsten die, welche die "Shield's" programmieren mit den Grundregeln der HW nahen nunmal Address & Pointer basierten C/C++ Programmierung vertraut sein.
Leider ist dies nicht immer der Fall. Nein, im Ernst, Du HAST Recht, wer hardwarenah "bastelt", sollte sich auch mit der zugehörigen programmierung auskennen. Auf der anderen Seite ist noch kein Meister vom Himmel gefallen, daher frage ich ja hier Ich halte mich für einen Einsteiger, dem die prozedurale und objektorientierte (und somit relativ abstrakte) Programmierung halbwegs vertraut ist. Ich gebe aber frei zu, dass ich mich abseits von Delphi weniger gut auskenne, besonders im hardwarenahen / lowlevel Bereich Soll heißen: Ist dir klar, das Delphi ClassObjekte nur "Zeiger" sind und nur Delphi RecordObjekte dem von dir hier gezeigtem C++ Style der Adruino direkt vergleichbar wären?
[...] Soweit so gut und einfach in "echtem" C++... einfach als Pointer anlegen und mit new erzeugen... mich wenig mit Pointern und "manueller" Speicher-Allokation beschäftigt habe.
Code:
Die Art und Weise, wie Du - vielen Dank im Übrigen für die Erklärung - in C++ die Instanz mit Pointern über new anlegst, war mir zwar (wenn auch eingerostet) vertraut,
anlegen
Valve *pinletValve; if(hasInValve) pinletValve = new Valve(...); else pinletValve=NULL; was mir aber nicht so klar war, war der Unterschied zwischen Pointern, Referenzen und automatischen/nicht-dynamischen Objekten und dass ich die anscheinend wild vermischt hatte.
Zitat von https://www.c-plusplus.net/forum/topic/332446/unterschied-referenz-zeiger:
eine Referenz kann nicht 0 sein, muss auf etwas vorhandenes initalisiert sein und darf nachträglich nicht geändert werden - das sind ganz ordentliche Unterschiede zu Zeigern
Zitat von http://cseweb.ucsd.edu/~kube/cls/100/Lectures/lec2/lec2-12.html:
Here x refers directly to an object, an instance of the class C (not a pointer!), created automatically on the stack, and initialized with the default constructor.
[...] C x [...]
Code:
bzw. FINletValve : TValve;
nur eine "leere / nicht initialisierte" Klassenvariable (mit dem Speicherverbrauch eines Pointers) anlege.
Valve inletValve
So ist es ja, wenn ich nicht vollkommen auf dem Schlauch stehe, in Delphi. Dass in C++ automatisch der Standard-Konstruktor aufgerufen und ein Objekt erzeugt und der Speicher reserviert wird, war mir so nicht klar. Ich dachte, dass eine Instanz erst erzeugt wird mit einem "expliziten" Constructor-Aufruf
Code:
.
inletValve = Valve(...)
Dem ist anscheinend nicht so. Das aber erklärt so einiges, mir sind ein paar Sachen klarer geworden. Dies sind eben die Unterschiede in den einzelnen Programmiersprachen, die mir weniger vertraut sind. Nochmals Danke an Euch.
Tobias
Bitte nicht hauen , ich weiß es nicht besser |
Zitat |
silver-moon-2000 |
Öffentliches Profil ansehen |
Mehr Beiträge von silver-moon-2000 finden |
Registriert seit: 15. Mär 2007 4.093 Beiträge Delphi 12 Athens |
#7
Zitat:
vor allem der SRAM ziemlich begrenzt ist,
nur ein minimales Memoryleak oder auch nur ein Timingproblem (ala normalerweise sind Obj1 und Objs niemals zusammen im Speicher, ausser in dem einem speziellen Fall, und schon passts nicht mehr in den Speicher) wird deine App da vor die Wand laufen lassen, und du bekommst womöglich nicht mal einen ordentlichen Stacktrace. Oder auch die Optimierungsstufen im Code, wo man nicht mehr genau weiss was das jetzt für Auswwirkungen hat, da optimiere ich lieber selbst und weiss wo es kracht. Wie gesagt, vielleicht haben die C++ Compiler da mittlerweile zugelegt, und lösen all diese Grenzfälle bei 8-Bit perfekt, würde mich freuen bei Erfolg mal eine Rückmeldung zu sehen Rollo |
Zitat |
Registriert seit: 6. Feb 2008 838 Beiträge |
#8
"mich wenig mit Pointern und "manueller" Speicher-Allokation beschäftigt habe. "
ein paar letzte Hinweise: 1. alles was du manuell dynamisch anlegst, musst du auch manuell wieder freigeben... 2. alles was der C++ Compiler statisch für dich anlegt, kannst und darfst du nicht manuell freigeben 3. den Unterschied zwischen statisch/automatisch vom Compiler erzeugten Objekten siehst du sehr gut um C/C++ Code... denn einmal schribst du o.??? und dynamisch o->??? also z.B. Valve.SwitchOn() bzw. Valve->SwichOn() 4. Wenn du lieber ohne NEW mit automatisch erzeugen Objekten arbeitest, lege deine "temporär" benötigten Objekte NICHT in der Klasse an, sondern erzeuge sie als Lokalvariablen in den Methoden, wo sie temporär lokal gebraucht werden... damit nutzt du die "ScopeBasierte" und vom C/C++ Compiler garantierte Speicherverwaltung bestmöglich und musst nicht bei jedem "return" irgendwo in einer Methode dran denken, welche Sachen an dieser Stelle vorher dynamisch manuell von dir erzeugt wurden und jeweils vor jedem "return" von dir auch passend wieder freigegeben werden müssen 5. auch wenn sich hier der Mehrheit wohl die Fußnägel kräuseln... unter 32Bit Embedded ist es keine Schande statt zig "return(1)" oder "return(0)" besser zig lokale "goto RetOK" bzw. "goto RetERR" zu schreiben, und jeweils via zwei lokaler "Sprungziele" am Ende jeder Funktion dort zentral manuell alles dynamische aufzuräumen. Ja, das is OldScool C-Style, aber try exept und das absichtliche "werfen mit Exeptions" erzeugen auf EmbeddetCPUs irwitzigen ASM Code und die Stackgröße kann explodieren, wenn real ein Fehler an einer Stelle kommt, wo der HW begrenzte Stack für den normal Ablauf gerade noch gereicht hat So ich programmiere heute noch 8Bit alles 256Byte-SRAM/4KbFlashROM über 1KB/32KB bis 4KB/64KB und weils funktioniert auch USB und TFT-Display taugliche 16BITer mit 32KB-RAM/256KB-FlashROM... halt mit eigener Hardware und nix Adruino, und weil ich zufällig auch echtes OO-C++ und Delphi kann, stehe ich absolut bewußt dazu für bestimmtes eben der Effektivität wegen auf kompkaten OldScool Style zu setzen... den ich auf Grund meines Alters halt zum Glück auch (noch) sehr gut kenne und selbst auch im Schlaf kann. Adruino&Co finde ich gut, denn so wird heute einer neuen Generation von Embedded Programmieren der Einstieg in die HW abhängige Entwicklung&Programmierung wesentlich erleichtert. Wenn es nicht um UltraLowPower oder die Einsparung von wenigen USD in Großserie geht, soll heut doch alles in C++ mit 16/32BITer gemacht werden. ab 64+KB-RAM und 512+KB-Programmspeicher ist USB, Grafik, Ethernet oder jegliche sonstige Kommunikation und IO-Steuerung heute fast völlig problemlos in C++ zu programmieren |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |