AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Assigned in C++

Ein Thema von silver-moon-2000 · begonnen am 26. Sep 2018 · letzter Beitrag vom 28. Sep 2018
Antwort Antwort
silver-moon-2000

Registriert seit: 18. Feb 2007
Ort: Schweinfurt
170 Beiträge
 
Delphi XE Professional
 
#1

Assigned in C++

  Alt 26. Sep 2018, 11:25
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:
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;
Doch leider ist Delphi nicht die Zielsprache In C++ schwebt es mir vor, das Ganze so umzusetzen:

Code:
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();
  }
}
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?

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
  Mit Zitat antworten Zitat
mensch72

Registriert seit: 6. Feb 2008
838 Beiträge
 
#2

AW: Assigned in C++

  Alt 26. Sep 2018, 11:59
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.
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.110 Beiträge
 
Delphi 10 Seattle Enterprise
 
#3

AW: Assigned in C++

  Alt 26. Sep 2018, 17:05
Kennt dein Compiler nullptr ?
  Mit Zitat antworten Zitat
mensch72

Registriert seit: 6. Feb 2008
838 Beiträge
 
#4

AW: Assigned in C++

  Alt 26. Sep 2018, 20:02
Kennt dein Compiler nullptr ?
-> das Problem ist, er hat keinen Computer-Compiler sondern einen 8Bit Microcontroler mit einstellig !KB!-RAM und 2stellig !KB! (Flash)Rom...
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
  Mit Zitat antworten Zitat
Rollo62

Registriert seit: 15. Mär 2007
3.908 Beiträge
 
Delphi 12 Athens
 
#5

AW: Assigned in C++

  Alt 27. Sep 2018, 18:20
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
  Mit Zitat antworten Zitat
silver-moon-2000

Registriert seit: 18. Feb 2007
Ort: Schweinfurt
170 Beiträge
 
Delphi XE Professional
 
#6

AW: Assigned in C++

  Alt 28. Sep 2018, 08:19
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...
Naja, ich arbeite mit dem Mega2560, habe also ~256kB Flash zur Verfügung, an den 8kB SRAM ändert sich jedoch nichts.

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 [...]
Zum Thema Objektorientierung:
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.
Du hast sooo Recht. Immer diese Maker & Noobs, die keine Ahnung haben, die kosten einem wirklich den letzten Nerv! Schlimm sowas!!! (diesen Satz bitte mit einer riesigen Portion Humor lesen! Schau doch auch einfach mal in meine Signatur)


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...
Auf der einen Seite ist mir durchaus klar, dass ein Delphi-"Klassenobjekt" eigentlich ein Pointer ist. Die Tatsache, dass bei Delphi jedoch "im Normalfall" eine direkte Zeiger-Manipulation selten vorkommt, hat dafür gesorgt, dass ich

mich wenig mit Pointern und "manueller" Speicher-Allokation beschäftigt habe.

Code:
anlegen
Valve *pinletValve;
if(hasInValve) pinletValve = new Valve(...); else pinletValve=NULL;
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,
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
[...]
Bisher dachte ich, dass ich mit
Code:
Valve inletValve
bzw. FINletValve : TValve; nur eine "leere / nicht initialisierte" Klassenvariable (mit dem Speicherverbrauch eines Pointers) anlege.
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
  Mit Zitat antworten Zitat
Rollo62

Registriert seit: 15. Mär 2007
3.908 Beiträge
 
Delphi 12 Athens
 
#7

AW: Assigned in C++

  Alt 28. Sep 2018, 08:53
Zitat:
vor allem der SRAM ziemlich begrenzt ist,
Genau deswegen traue ich dem C++ Compiler bei begrenzten Ressourcen da nicht so ganz, Objekte anlegen, löschen, etc.,
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
  Mit Zitat antworten Zitat
mensch72

Registriert seit: 6. Feb 2008
838 Beiträge
 
#8

AW: Assigned in C++

  Alt 28. Sep 2018, 10:19
"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
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es 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

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:57 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz