Helmut A. schrieb:> Ich das richtig, dass Variante 1 mehr Speicherplatz braucht als> Variante 2?
nein, bei Variante 1 wird der Speicherplatz nur während des
Funktionsaurufs gebraucht, bei 2 dauerhaft
Helmut A. schrieb:> sehe Ich das richtig, dass Variante 1 mehr Speicherplatz braucht als> Variante 2?
Der Speicherort und die Lebensdauer sind unterschiedlich. So leben die
lokalen Variablen nur während dem Funktionsaufruf auf dem Stack. Die
globalen Variablen hingegen, während der gesamten Programmdauer.
https://www.uninformativ.de/blog/postings/2015-10-25/0/POSTING-de.html
Helmut A. schrieb:> sehe Ich das richtig, dass Variante 1 mehr Speicherplatz braucht als> Variante 2?
Nein.
Neben den Stichworten static und Stack musst Du auch noch für deinen
Prozessor/Compiler herausfinden, wie gut er 2 identische Strings
zusammenfasst, bzw ob er überhaupt aus Rom lesen kann, da Du hier ja 2
völlig verschiedene Codestücke vergleichst.
Heißt das, wenn Ich in zwei verschiedenen Funktionen innerhalb der
Funktionen z.B "char buff[10]" anlege und in der anderen auch, werden
mir dann 20 Bytes berechnet im Speicherverbrauch? Oder bekommt der
Kompiler mit, dass er die auch zusammenlegen könnte?! So das Ich quasie
nur 10 Bytes brauche insgesamt.
Nur wenn eine von dieser Funktionen aus der anderen gerufen wird.
Sonst wird erste Funktion gerufen, Speicherplatz belegt. Funktion zu
Ende = Speicherplatz frei. Zweite Funktion nimmt wieder mal 10 bytes. Am
Ende sind die wieder frei.
Maxim B. schrieb:> Nur wenn eine von dieser Funktionen aus der anderen gerufen wird.> Sonst wird erste Funktion gerufen, Speicherplatz belegt. Funktion zu> Ende = Speicherplatz frei. Zweite Funktion nimmt wieder mal 10 bytes. Am> Ende sind die wieder frei.
Also spielt es wenn Ich sie nicht gerade wo anders brauche die Variable
keine Rolle ob Ich sie in der Funktion deklariere oder extern. Solange
Ich sie nicht zusammen aufrufe?!
Eine Rolle spielt hier, wie lange Array lebt. Global, static oder nicht.
Wenn nichts anderes steht, lebt eine Datei, die innerhalb von einer
Funktion deklariert, nur so lange wie die Funktion selbst.
Willst du, dass die Datei auch danach ihre Wert für nächste Ausführung
von dieser Funktion behält - so muss du sie als static deklarieren (dann
bleibt die Datei in RAM, ist aber nicht aus anderen Funktionen
sichtbar).
Willst du die Datei für alle zugänglich machen, so muss du die als
global deklarieren.
Noch eine Variante: Datei als static, und Zugang für alle anderen nur
über diese Funktion.
Also, kuck mal in Lehrbuch über Lebzeiten von Variablen.
Wan eine Variable nur local in eine Function verwendet wird, soll die am
besten auch local declariert werden. Globale Variablen nur wen absolut
notwendig verwenden. Grund ist das bei grossere Programme die Structure
uebersichtlich zu halten, und bugs zu vermeiden. Stellen sie sich for
das sie eine Program hatte wo X-Tausend variablen von ueberall in
Program zuganglich sein....
Speicherplatz ist mit die heutige µ-processoren meist kein Thema mehr.
Helmut A. schrieb:>> sehe Ich das richtig, dass Variante 1 mehr Speicherplatz braucht
Die Frage ist schon schlecht gestellt. "Speicherplatz" ... meint was? Es
gibt verschiedenen Speicher in einem µC.
> Wenn man die Variable einmal global deklariert, so das die anderen> Funktion darauf zugreifen können.
Du vergleichst Äpfel mit Birnen. Nur wenn du das Array global
definierst, kannst du überhaupt von verschiedenen Stellen darauf
zugreifen. Die Arrays innerhalb der Funktionen sind auch nur innerhalb
der jeweiligen Funktion überhaupt sichtbar. Mehr noch: sie werden erst
dann angelegt, wenn die Funktion aufgerufen wird und beim Verlassen der
Funktion wieder zerstört.
Du möchtest jetzt dein C-Lehrbuch aufschlagen und über die Lebensdauer
und Sichtbarkeit von globalen und lokalen Variablen nachlesen.
Axel S. schrieb:> Du möchtest jetzt dein C-Lehrbuch aufschlagen und über die Lebensdauer> und Sichtbarkeit von globalen und lokalen Variablen nachlesen.
Das habe Ich soweit verstanden.
Es geht mir mehr oder weniger darum, wie viel Speicherplatz letzendlich
belegt wird.
Wenn ich zwei Funktionen habe (die eigentlich keine globale Variable
brauchen) und die auf eine Variable drauf zugreifen die z.B 10 Bytes
belegt. Brauche Ich nur "10" Bytes.
Wenn Ich jetzt innerhalb der beiden Funktionen jeweils ein Array mit
"10" Bytes anlege, würde ich ja "20" Bytes benötigen in diesem moment
oder?
Helmut A. schrieb:> Wenn Ich jetzt innerhalb der beiden Funktionen jeweils ein Array mit> "10" Bytes anlege, würde ich ja "20" Bytes benötigen in diesem moment> oder?
in "diesem Moment" bist du aber nur in einer Funktion (ich nehme mal an
dass du mit "rekursiv" noch nichts anfangen kannst), also nur 10 Bytes
Helmut A. schrieb:> Wenn Ich jetzt innerhalb der beiden Funktionen jeweils ein Array mit> "10" Bytes anlege, würde ich ja "20" Bytes benötigen in diesem moment> oder?Maxim B. schrieb:> Nur wenn eine von dieser Funktionen aus der anderen gerufen wird.
Walter schrieb:> Helmut A. schrieb:>> Wenn Ich jetzt innerhalb der beiden Funktionen jeweils ein Array mit>> "10" Bytes anlege, würde ich ja "20" Bytes benötigen in diesem moment>> oder?>> in "diesem Moment" bist du aber nur in einer Funktion (ich nehme mal an> dass du mit "rekursiv" noch nichts anfangen kannst), also nur 10 Bytes
1
uint8_ttest1(uint8_tbyte)
2
{
3
charbuff[10]="";
4
}
5
uint8_ttest2(uint8_tbyte)
6
{
7
charbuff[10]="";
8
}
9
uint8_ttest3(uint8_tbyte)
10
{
11
charbuff[10]="";
12
}
13
14
uint8_ttest4(uint8_tbyte)
15
{
16
charbuff[10]="";
17
}
Genau das wollte Ich wissen!
Wenn ich jetzt diese 4 Funktionen habe, die sich aber selbst nicht
untereinander aufrufen, benötige Ich dann nur 10 Bytes.
Habe vorher angenommen, dass mir 40 Bytes flöten gehen.
Stefan U. schrieb:>> benötige Ich dann nur 10 Bytes.>> Ja, weil das Array nur temporär auf dem Stack liegt.
Das heißt wiederum Ich darf nur so viel Speicherplatz brauchen, wie groß
der Stack ist?
Nein, der Compiler hat keine Ahnung, wie viel Stack später zur Laufzeit
verfügbar ist.
Wer ESP8266 unter Arduino programmiert, stößt bei String-Manipulationen
sehr schnell an diese grenze.
Stefan U. schrieb:> Nein, der Compiler hat keine Ahnung, wie viel Stack später zur Laufzeit> verfügbar ist.
Der Compiler nicht, aber der Linker schon. (Ich nehme mal an dass es
hier um "bare metal" Programmierung geht.)
Helmut A. schrieb:> Axel S. schrieb:>> Du möchtest jetzt dein C-Lehrbuch aufschlagen und über die Lebensdauer>> und Sichtbarkeit von globalen und lokalen Variablen nachlesen.> Das habe Ich soweit verstanden.
Das bezweifle ich.
> Wenn ich zwei Funktionen habe (die eigentlich keine globale Variable> brauchen) und die auf eine Variable drauf zugreifen die z.B 10 Bytes> belegt. Brauche Ich nur "10" Bytes.> Wenn Ich jetzt innerhalb der beiden Funktionen jeweils ein Array mit> "10" Bytes anlege, würde ich ja "20" Bytes benötigen in diesem moment> oder?
Es würde wesentlich mehr Spaß machen mit dir zu reden, wenn ich den
Eindruck gewinnen könnte, daß du auch verstehst was ich dir sage.
Lokale Variablen werden genau in dem Moment angelegt, wenn die
entsprechende Funktion aufgerufen wird. Und beim Verlassen der Funktion
wieder gelöscht. Da dein Programm sich nicht gleichzeitig in beiden
Funktionen befinden kann, wird immer nur höchstens eine der beiden
Funktionen ihre 10 Bytes für lokale Variablen brauchen. Und so lange
keine der beiden Funktionen aktiv ist, wird gar kein Speicher für die
jeweiligen lokalen Variablen gebraucht.
Interessant wird es, wenn Funktionen sich gegenseitig aufrufen,
möglicherweise sogar noch rekursiv. Dann müssen die lokalen Variablen
aller Funktionen in so einer Aufruf-Kaskade ins RAM (für gewöhnlich: in
den Teil des RAMs, der für den Stack zur Verfügung steht) passen.
Wenn die Aufruf-Kaskade jetzt noch davon abhängig ist, welche Daten
gerade verarbeitet werden, dann wird es mindestens schwierig, in vielen
Fällen sogar unmöglich, den maximal benötigten Speicher im Voraus
anzugeben.
Legst du innerhalb einer Funktion eine Variable an, dann liegt sie auf
dem Stack. Verlässt du die Funktion wieder, wird der Speicherplatz
automatisch freigegeben. Auf dem Stack liegen außerdem noch ein paar
andere Dinge.
Legst du die Variable stattdessen global an (oder innerhalb der Funktion
mit "static", was im Prinzip das gleiche ist), dann liegt sie im
Datensegment. Dort existiert sie während der gesamten Laufzeit deines
Programmes, belegt also auch dann Speicher, wenn du gerade nichts mit
ihr anstellst.
Der Linker legt die Größe des Datensegmentes so fest, dass alle globalen
und statischen Variablen dadrin Platz haben. Er weiß also, wieviel
Speicher du dort benutzt und kann dich warnen, wenn es zu viel ist.
Der Teil des RAMs, der nicht für das Datensegment (und den Heap, aber
den ignoriere ich hier) reserviert ist, steht dir als Stack zur
Verfügung. Benutzt du mehr Stack-Speicher, als gerade frei ist, dann
fliegt dir dein Programm um die Ohren oder fängt an, sich subtil komisch
zu verhalten (sehr schwer zu debuggen!). Wieviel Speicher auf dem Stack
frei ist, wissen weder Compiler noch Linker, also gibt es auch keine
Warnungen.
Und zu guter Letzt: Auf einem Mikrocontroller bekommst du für
ungenutzten Speicher kein Geld zurück. Lieber 40 Byte im Datensegment
"verschwenden" und sicher sein, dass der Speicher reicht, als manchmal
einen Stack-Overflow produzieren - gilt natürlich nur, wenn du genug
Speicher hast.
Walter K schrieb:> Ich habe auch noch nie verstanden, weshalb immer wieder geglaubt wird,> man müsse globale Variablen auf Teufel komm raus verhindern.
Dann wirst es wohl auch nie mehr kapieren.
Trotzdem zwei wichtige Punkte:
- mit globalen Variablen sind keine mehrfachen Instanzen von "Objekten"
möglich.
- globale Variablen machen es schwer den Überblick über Seiteneffekte zu
bewahren.
Walter K schrieb:> man müsse globale Variablen auf Teufel komm raus verhindern.
Tja, es ist nunmal best practice und findet auch beim Compiler
Anerkennung und ist daher sehr wohl sinnvoll.
Benutzt man eine Variable nur in einem Scope kann der Compiler
wesentlich besser optimieren da er so weiß, dass die Variable sonst
nirgendwo angerührt oder benötigt wird. Benutzt man globale Variablen
muss das erst aufgelöst werden und durch irgendwelche Abhängigkeiten
lassen sich dann diverse Optimierungen nicht anwenden. Was g++ da
inzwischen kann ist echt genial.
Cyblord -. schrieb:> Trotzdem zwei wichtige Punkte:>> - mit globalen Variablen sind keine mehrfachen Instanzen von "Objekten"> möglich.>> - globale Variablen machen es schwer den Überblick über Seiteneffekte zu> bewahren.
2 Probleme, die man in speziell in Mikrocontrollern oft gar nicht
findet.
Man muß beim SW-Design auch die Platform berücksichtigen.
batman schrieb:> 2 Probleme, die man in speziell in Mikrocontrollern oft gar nicht> findet.
Unsinn. Es gibt mehr Mikrocontrollerprojekte als ein LED-Blinkprogram.
Vielleicht ist deine Erfahrung hier nicht ganz so repräsentativ wie du
denkst.
> Man muß beim SW-Design auch die Platform berücksichtigen.
Nein, grundlegende Konzepte sind da völlig unabhängig. Man kann
allerdings bei deren Umsetzung die Spezialitäten der Plattform
berücksichtigen. Grundsätzlich gelten sie aber dennoch.
Es gibt keine guten Gründe für schlechten Code. Es bleibt dann eine
Ausrede. So eine kann ich für jede Plattform und für jedes Problem
finden.
Stoppt mal bitte..... und jetzt erstmal tief durchatmen.
Ich denke, dass die Vor- und Nachteile von Heap und Stack ausreichend
beleuchtet wurden.
Und ich hoffe, dass wir hier vernünftig genug sind, nicht "den einen
einzig wahren Weg" propagieren zu wollen. Sind wir doch, oder?
Walter K schrieb:> Cyblord -. schrieb:>> - mit globalen Variablen sind keine mehrfachen Instanzen von "Objekten">> möglich.>> Wo sind mehrfache Instanzen relevant?
Praktisch überall. Wo hat man KEINE Instanzen? Allein diverse
Interfaces, z.B. UART oder SPI oder was anderes. Man kann ein Modul
schreiben um deren Funktionalität zu kapseln, und das dann aber einfach
auf beliebig viele reale Interfaces aufsetzen. Dazu muss man aber eben
die gesamten Zustandsinformationen für eine solches Modul in einer
Struktur halten und den Funktionen mitgeben. Das ist ja quasi die
OOP-Zu-Fuss Methode, wie sie bei C schon seit Anbegin der Zeit angewandt
wird.
Globale Variablen sind auch nicht per se verboten. Wenn eine solche
Variable Teil des Gesamtzustandes des Gerätes ist, kann man sie auch
einfach global definieren. Aber meist werden halt dann schnell mal
temporäre Variablen global gemacht, und eben nur lokal verwendete
Zustände, von z.B. oben genannten Interface. Und das ist halt dann kein
sauberer Stil mehr sondern Pfusch.
Ausgerechnet UART findet in vielen Tinys gar keine Verwendung und
entsprechend selten braucht man generell mehrere davon parallel.
Mit deinen Designrules und -Patterns aus der OO-Kiste wirst du bei
kleinen Mikrocontrollern nicht erst beim Tiny10 scheitern. Ok, sowas
benutzt man in deiner Welt halt einfach nicht.
batman schrieb:> Ausgerechnet UART findet in vielen Tinys gar keine Verwendung und> entsprechend selten braucht man generell mehrere davon parallel.
Das waren Beispiele. Fast jede Controlleranwendung hat ein Interface
nach aussen. Neben dem reinen physikalischen Interface hockt da ja meist
ein übergeordnete Protokoll drauf, und sei es nur ein Steuerbit oder
eine Checksumme oder eine Auswertlogik um 2 Pins abzufragen. Und schon
hat man eine Funktionalität, die man ohne solche Konzepte nicht einfach
und sauber duplizieren kann, wenn man eben mal 2 oder 3 davon braucht.
Das kommt ich kleinsten Controller vor und kann im kleinsten Controller
sauber umgesetzt werden.
> Mit deinen Designrules und -Patterns aus der OO-Kiste wirst du bei> kleinen Mikrocontrollern nicht erst beim Tiny10 scheitern. Ok, sowas> benutzt man in deiner Welt halt einfach nicht.
Unsinn. Ich arbeite mit ATTinys seit Jahren.
Du musst mir nicht erzählen was man da braucht oder nicht braucht. Was
du tust, sind ausreden erfinden für deinen Pfusch-Code. Dafür kann der
Controller aber nichts.
Natürlich ist ein Controller ohne SRAM ein unnützes Beispiel, weil es
hier um C geht und nicht um Assembler. Natürlich kann man sich immer
einen so kleinen eingeschränkte Controller überlegen, bei dem man nichts
mehr tun kann ,als 10 ASM Befehle gerade noch linear hintereinander
abzuarbeiten.
Aber so sieht die Embedded Welt nicht aus. Schon auf normalen Tinys und
Megas kann man mit solchen Konzepten gut arbeiten. Der Tiny841 hat z.B.
2 UARTs und genug Flash und SRAM.
Dafür braucht man nicht mal 32 Bitter.
Deine Beispiele taugen also nur zum plumpen polemisieren.
>...Das ist ja quasi die OOP-Zu-Fuss Methode, wie sie bei C schon seit Anbegin der
Zeit angewandt wird.
Für mich ist das OOP - Blah Blah!
Unglaublich viele Begriffe und Regeln - und nichts dahinter!
Natürlich hat auch OO seine Berechtigung - aber man sollte prozedurale
Programmierung nicht einfach als „Pfusch „ bezeichnen
Walter K schrieb:>>...Das ist ja quasi die OOP-Zu-Fuss Methode, wie sie bei C schon seit Anbegin
der
> Zeit angewandt wird.>> Für mich ist das OOP - Blah Blah!
Ja natürlich so habe ich dich auch eingeschätzt.
> Unglaublich viele Begriffe und Regeln - und nichts dahinter!
Du verstehst den Sinn nicht, das ist alles. Das ist aber auch nicht
schlimm. Ich kann auch nicht Geige spielen. Macht mir nichts.
> Natürlich hat auch OO seine Berechtigung - aber man sollte prozedurale> Programmierung nicht einfach als „Pfusch „ bezeichnen
Es geht aber nicht um OOP. Es geht um Kapselung und es geht um
Abstraktion. Dafür braucht es kein OOP. Das geht auch prozedural.
Aber prozedural bedeutet eben nicht automatisch den Verzicht auf oben
genanntes.
Es bedeutet NICHT das man einfach alle Variablen global macht und dann
so lange im Code darauf rumschreibt bis ab und zu mal korrektes
passiert. Das kann ich mit OOP Sprachen aber auch machen.
Es geht also darum ob man sauber programmiert oder nicht. Und ignoriert
man Dinge wie Kapselung und Abstraktion dann programmiert man eben nicht
sauber. Egal unter welchen Schlagworten.
Aber dann sage uns doch mal, wenn du z:B. ein Protokoll auf UART
aufsetzen würdest, z.b. DMX. Wie würdest du dann das ganze Konzipieren
um es auch variabel auf 2 oder 3 Uarts nutzen zu können? Wie sähe dein
prozeduraler Ansatz hier aus? Werden wir mal konkret.
„...
Es bedeutet NICHT das man einfach alle Variablen global macht und dann
so lange im Code darauf rumschreibt bis ab und zu mal korrektes
passiert...“
Erst unterstellst Du Anderen sowas - und belehrst Du, dass es so nicht
geht.
Wer hat hier irgendwann behauptet, alle Variablen müssten global gemacht
werden?
Walter K schrieb:> Ich habe auch noch nie verstanden, weshalb immer wieder geglaubt wird,> man müsse globale Variablen auf Teufel komm raus verhindern.
Muss man auch nicht. Aber man tut gut daran nach dem Motto vorzugehen:
So viel global wie nötig - so wenig wie möglich.
Cyblord -. schrieb:> Der Tiny841 hat z.B.> 2 UARTs und genug Flash und SRAM.
Ich würde trotzdem einfach den Code duplizieren und ein putchar0,
putchar1 usw. schreiben, die direkt auf IO-Register zugreifen, als
umständlich über viele Pointerinstanzen auf eine Struct.
Das Duplizieren spart RAM, ist deutlich schneller und dürfte sogar
weniger Code-Flash kosten.
Peter D. schrieb:> Ich würde trotzdem einfach den Code duplizieren und ein putchar0,> putchar1 usw. schreiben, die direkt auf IO-Register zugreifen,
Ich weiß, ich kenne deinen Code.
So stolz wäre ich auf eine solche Einstellung aber nicht.
Außerdem geht es ja nicht um das putchar0 oder putchar1. Irgendwo im
Code muss man ja auf den jeweiligen konkreten UART gehen. Und putchar
tut ja auch nichts als ein Byte rauszuschreiben. Da ist ja quasi keine
Funktionalität dahinter.
Ich spreche auch nicht von Pointern auf die IO Register. Denkst du ich
will dass mein XYZ-Modul irgendwas von IO-Registern oder HW-Adressen
meinen Controllers weiß?
Aber ich sprach von einem übergeordneten Protokoll welches einen UART
verwendet. D.h. es geht darum eine spezielle Funktionalität zu kapseln.
Die eben nicht trivial ist. Also wie kommt dieses Modul an dein putchar0
oder putchar1? Kopierst du den gesamten Code des (z.B. DMX oder besser
Modbus-)Moduls und setzt jeweils überall putchar0 und putchar1 ein?
Deine Antwort macht mir eher deutlich dass du aus dem klein-klein deiner
Hardwareregister denktechnisch gar nicht rauskommst.
Cyblord -. schrieb:> Deine Beispiele taugen also nur zum plumpen polemisieren.Cyblord -. schrieb:> Deine Antwort macht mir eher deutlich dass du aus dem klein-klein deiner> Hardwareregister denktechnisch gar nicht rauskommst.
Immer wieder erstaunlich, wie Menschen anderen Menschen das vorwerfen,
was sie selbst auch betreiben.
Cyblord -. schrieb:> Wie würdest du dann das ganze Konzipieren> um es auch variabel auf 2 oder 3 Uarts nutzen zu können? Wie sähe dein> prozeduraler Ansatz hier aus?
Wenn das echt parallel laufen soll, muss man bei 2-3 UARTs aber sowas
wie DMA einsetzen - und damit sieht der Code dann GANZ anders aus.
Die Vorstellung auf einem µC einfach was objektorientiert zu machen ist
etwas naiv. Sobald etwas "nebenher" laufen muss (z.B. Tastenabfrage oder
anderer Input) muss das im Code auch berücksichtigt werden (Stichwort:
Delay).
Beispiel UART: Normaler Beispielcode wartet praktisch immer darauf das
TX-Buffer leer ist. Wenn man das jetzt einfach objektorientiert kapselt
dann sender man einen String bei 3 UARTs nur jeweils auf einem zur
gleichen Zeit.
Für überschneidenes Senden müsste man dann auf andere Tricks (wie o.g.
DMA) zurück greifen. Dann ist aber z.B. die RS485 Umschaltung plötlich
ein Problem, denn die darf natürlich nur nach dem Senden passieren.
TL;DR Objektorientiert kann man machen, aber man muss das Gesamtbild im
Auge behalten.
Jim M. schrieb:> Wenn das echt parallel laufen soll, muss man bei 2-3 UARTs aber sowas> wie DMA einsetzen - und damit sieht der Code dann GANZ anders aus.
Unsinn. Empfangen per Interrupt reicht aus. Die Performance muss
natürlich dem des System angepasst sein.
> Normaler Beispielcode
Wir reden hier von Produktivcode, Was hat irgendein Beispielcode damit
zu tun? Nirgendwo in realen Projekten wartet ein UART auf den Empfang
und blockiert dabei das Restsystem.
Aber das ist doch gar nicht der Punkt.
Und ja, man kann sich über SW-Architektur unterhalten ohne in
Nebenkriegsschauplätze abzutauchen. Versuchs mal.
Cyblord -. schrieb:> Kopierst du den gesamten Code des (z.B. DMX oder besser> Modbus-)Moduls und setzt jeweils überall putchar0 und putchar1 ein?
Ich hatte bisher nicht die Notwendigkeit, mehrere DMXe, Modbusse usw. in
einem MC zu verwenden. Daher wird das putchar0 oder putchar1 über
Defines in das jeweilige Protokoll eingebunden.
Cyblord -. schrieb:> Deine Antwort macht mir eher deutlich dass du aus dem klein-klein deiner> Hardwareregister denktechnisch gar nicht rauskommst.
Es sei mir erlaubt, daß ich ohne jeden Grund Code auch nicht unnötig
aufblase.
Peter D. schrieb:> Cyblord -. schrieb:>> Kopierst du den gesamten Code des (z.B. DMX oder besser>> Modbus-)Moduls und setzt jeweils überall putchar0 und putchar1 ein?>> Ich hatte bisher nicht die Notwendigkeit, mehrere DMXe, Modbusse usw. in> einem MC zu verwenden.
Hmm ok. Das finde ich jetzt doch ein bisschen Arm. Da hätte ich etwas
mehr erwartet als ein "brauch ich nicht, hab ich nie gebraucht".
Bestreitest du denn die Möglichkeit dass es jemals Notwendig sein
könnte?
Wie WÜRDEST du es machen WENN du es mal brauchen würdest.
Außerdem bedenke, es sind nur Beispiele. Das Szenario ist nicht auf
irgendwelche Bussysteme beschränkt, es ist nur für gänginge Controller
ein häufiges Szenario. Nun gut, wohl nicht bei jedem.
> Es sei mir erlaubt, daß ich ohne jeden Grund Code auch nicht unnötig> aufblase.
Moment, und das von jemandem der "Duplizieren" als Lösung präsentiert?
Außerdem: Das tue ich auch nicht. Ich sehe auch nicht wie Code durch
eine sinnvolle Kapselung überhaupt aufgeblasen werden könnte. Schon gar
nicht sinnlos. Hast du da mal ein abschreckendes Beispiel?
Mark B. schrieb:> Stefan U. schrieb:>> Nein, der Compiler hat keine Ahnung, wie viel Stack später zur Laufzeit>> verfügbar ist.>> Der Compiler nicht, aber der Linker schon.
hilft aber leider auch nicht weiter wenn der Linker weiß wieviel Stack
zur Verfügung, aber nicht weiß wieviel Stack das Programm braucht
Walter schrieb:>> Der Compiler nicht, aber der Linker schon.>> hilft aber leider auch nicht weiter wenn der Linker weiß wieviel Stack> zur Verfügung, aber nicht weiß wieviel Stack das Programm braucht
Völlig richtig. Natürlich kann auch der Linker nicht wissen wie oft eine
Funktion ggf. rekursiv aufgerufen wird und wie viel Stack verbraucht
wird.
batman schrieb:> Dafür nimmt man dann einfach einen MC mit RAM im Überfluß.
Und ich dachte immer der Programmierer hat das im Griff, wie groß der
Stack wird.
Aber in deiner Welt kopiert man wohl irgendwelche Codefetzen zusammen
und lässt sich überraschen wie viel Speicher verbraucht wird.
Cyblord -. schrieb:> Wie WÜRDEST du es machen WENN du es mal brauchen würdest.
Da laß ich mich überraschen.
Ich hab zwar auf nem ATmega2560 alle 4 UARTs verwendet, aber für völlig
unterschiedliche Aufgaben.
Eine liest als SPI im Timerinterrupt Tasten, Encoder ein und gibt LEDs
aus.
Eine als SPI setzt DACs und liest ADCs.
Eine steuert als UART ein eDIPTFT43 an.
Und die 4. macht Debugausgaben.
Die Aufgaben sind also so unterschiedlich, da läßt sich nichts
modularisieren.
Daneben gibt es noch I2C, CAN und Ethernet auf der Platine. Wobei ich
CAN und Ethernet nicht selbst geschrieben habe. Ich kriege nur Puffer
mit Empfangsnachrichten übergeben und schreibe in nen Sendepuffer
zurück.
Ich bin auch kein 5d/8h Programmierer. Ich entwickele hauptsächlich
Hardware. Allerdings habe ich gemerkt, daß es sinnvoll ist, die
hardwarenahe Firmware selber zu stricken. Es ist nur schwer für einen
reinen Softwerker, alle Details der Hardware zu verstehen, d.h. das
würde viel Zeit kosten und fehlerträchtig sein. Daher mache ich einen
kleinen Teil Software mit.
Oft weiß man auch nicht, ob ein Fehler in Hardware oder Software liegt
und dann muß man sich eben Testroutinen selber schreiben können. Oder
auch Workarounds, die Hardwarefehler korrigieren.
Cyblord -. schrieb:> Und ich dachte immer der Programmierer hat das im Griff, wie groß der> Stack wird.
Das denken wohl viele. In der Realität wissen sie es nicht.
Cyblord -. schrieb:> Hmm ok. Das finde ich jetzt doch ein bisschen Arm.
Ich mache es wie Peter. Wobei der C-Code dabei automatisch kopiert wird,
wenn die Register und Funktionen gleich, nur an anderen Stellen sind
(aus sio1.c mit REG_X_SIO1 wird dann sio2.c mit REG_X_SIO2).
Für die Auswahl im Code gibt es bei mir je nach Familie 3 Möglichkeiten:
1) Hard kodiert Zuordnung (Modul X hat SIO1)
2) #define ModulX_Sio SIO1
3) Eine Struktur, in der die Funktionspointer der Sio zusammengefasst
sind (Der Kontext ist ja in der Datei sioX.c). Die Verlinkung erfolgt
dann, indem Modul X mit einem Pointer auf Sio1 initialisiert wird
Das Dritte verwende ich seit Jahren auf PIC18 (seit Hi-Tech
Funktionspointer unterstützt)
Achim S. schrieb:> Cyblord -. schrieb:>> Hmm ok. Das finde ich jetzt doch ein bisschen Arm.>> Ich mache es wie Peter. Wobei der C-Code dabei automatisch kopiert wird,> wenn die Register und Funktionen gleich, nur an anderen Stellen sind> (aus sio1.c mit REG_X_SIO1 wird dann sio2.c mit REG_X_SIO2).
Und warum benutzt Du dafür nicht gleich C++ und templates?
Peter D. schrieb:> Wilhelm M. schrieb:>> Und warum benutzt Du dafür nicht gleich C++ und templates?>> Ganz einfach, ich kann kein C++.
Doch, Du kannst es ja schon ;-) Was Dir fehlt ist das Wissen um
templates ...
Peter D. schrieb:> Wilhelm M. schrieb:>> Doch, Du kannst es ja schon ;-) Was Dir fehlt ist das Wissen um>> templates ...>> Z.B. in diesem Thread:> Beitrag "constexpr Argument-Wrapper"> habe ich 0,nix verstanden.
Oh ja, constexpr kann bissl tricky sein ...
Aber in diesem Fall hier geht es ja um die simpelste Anwendung von
templates zur Parametrierung von Funktionen (-> Funktionstemplates). Die
Grundlagen, die Du für dieses Problem brauchst, hast Du Dir in 30min
angelesen.
Wilhelm M. schrieb:> Aber in diesem Fall hier geht es ja um die simpelste Anwendung von> templates zur Parametrierung von Funktionen (-> Funktionstemplates).
Ich bräuchte weniger Templates für die Funktionen, denn die sind ja alle
verschieden, sondern eher für die 4 UARTs oder 4 Timer, damit ich einer
Funktion eine davon zuweisen kann.
Ist zugegeben aber eine rein theoretische Frage, denn die Zuordnung ist
ja durch die Hardware fest vorgegeben.
Peter D. schrieb:> Wilhelm M. schrieb:>> Aber in diesem Fall hier geht es ja um die simpelste Anwendung von>> templates zur Parametrierung von Funktionen (-> Funktionstemplates).>> Ich bräuchte weniger Templates für die Funktionen, denn die sind ja alle> verschieden, sondern eher für die 4 UARTs oder 4 Timer, damit ich einer> Funktion eine davon zuweisen kann.
Ich hatte Dich so verstanden, dass Dein putchar0() und putchar1() bis
auf die verwendeten SFRs identisch sind.
Wilhelm M. schrieb:> Ich hatte Dich so verstanden, dass Dein putchar0() und putchar1() bis> auf die verwendeten SFRs identisch sind.
In einem älteren Projekt hatte ich das so gemacht und dann per Define
ausgewählt, welches ich verwende. So richtig hat mich das aber auch
nicht vom Hocker gehauen.
Ich hätte gern, daß ich nur ein putchar() habe und dann mit "use UART0"
auswählen kann, welcher Port benutzt wird.
Peter D. schrieb:> Wilhelm M. schrieb:>> Ich hatte Dich so verstanden, dass Dein putchar0() und putchar1() bis>> auf die verwendeten SFRs identisch sind.>> In einem älteren Projekt hatte ich das so gemacht und dann per Define> ausgewählt, welches ich verwende. So richtig hat mich das aber auch> nicht vom Hocker gehauen.> Ich hätte gern, daß ich nur ein putchar() habe und dann mit "use UART0"> auswählen kann, welcher Port benutzt wird.
Du könntest ein Funktionstemplate putchar<>() schreiben und zwei
Parameter-Trait-Typen Uart0 und Uart1 (die Du aber auch wieder zu einem
Klassentemplate zusammen fassen könntest). Damit könntest Du schreiben:
1
structUart0{...};
2
structUart1{...};
3
4
template<typenameUART>
5
voidputchar(charc){...}
6
...
7
8
putchar<Uart0>(...);
9
putchar<Uart1>(...);
Allerdings würde man putchar() eher als Elementfunktion eines
Klassentemplates schreiben. Etwa:
Peter D. schrieb:> Wilhelm M. schrieb:>> Und warum benutzt Du dafür nicht gleich C++ und templates?>> Ganz einfach, ich kann kein C++.
Gibt viele die C++ wie C nutzen. Man verpasst dabei halt nur leider
viel, aber das gibt selbst für Leute die vor Jahren C++ gelernt haben,
selbst wenn man vor Jahren mit C++11 angefangen hat sind die neuen
Features un C++14 und gerade C++17 genial.
Man muss halt dauernd auf dem Laufenden bleiben. Bei µC ist das
natürlich etwas schwerer da hier teils maximal C++11 möglich ist, aber
gerade auf x86_64 bzw. ARM bekommt man so viele Komfortfunktionen mit
jeder neuen Version dass es sich schlichtweg lohnt diese einzusetzen.
http://www.stroustrup.com/4th.html
Das ist gut aber naja, es ist schon sagen wir etwas schwere Kost und
teils zu ausführlich, für den Einstieg ist das hier auch nicht all zu
schlecht:
https://www.amazon.de/Effective-Modern-Specific-Improve-2014-12-05/dp/B0184WO96Q/
Beide Bücher findet man bei div. Suchseiten zum probelesen in digitaler
Form, alte Auflagen liegen teils frei auf Uniservern. 90€ solltest du da
nicht gleich ausgeben. ;)
Aber selbst die Wikipedia Artikel sind relativ okay inzwischen.
https://de.wikipedia.org/wiki/Template_(C%2B%2B)
Man hat auch immer wieder das Problem, etwas erstmal so machen zu
müssen, daß es funktioniert. Um es dann auch noch schön zu machen, fehlt
oft die Zeit.
Einer Funktion eine beliebige UART zuweisen zu können, wäre zwar hübsch.
Aber nicht notwendig, denn der Schaltplan der Platine steht ja schon
fest.
Peter D. schrieb:> Einer Funktion eine beliebige UART zuweisen zu können, wäre zwar hübsch.> Aber nicht notwendig, denn der Schaltplan der Platine steht ja schon> fest.
Wenn du deine Software sauber versionierst und immer per Git/etc.
verfügbar hast ist es halt praktisch sowas auszulagern an eine Lib.
Entdeckst du einen Bug kannst du so diesen gleichzeitig in all deinen
Anwendungen beheben. Ist die API für UART sauber implementiert in deiner
Lib kannst du sogar den µC für einen ganzen Haufen Projekte ohne viel
Arbeit wechseln. Musst du da jeweils einzelne Funktionen usw. und
hartcodierte Werte anpassen ist das relativ schnell fehlerträchtig und
man macht sich nur unnötig Arbeit.
Selbst wenn du die Funktion nicht in eine Lib verlagerst kannst du so
ziemlich simpel mit sed/etc. mehrere Dateien schnell überarbeiten.
Zudem steigt dadurch die Lesbarkeit des Codes.
Damit sind wir wieder an dem üblichen Punkt der Diskussion angekommen:
jemandem die Vorteile eines besseren Werkzeuges klar zu machen, wenn er
subjektiv noch nie wirklich unter den Nachteilen des schlechteren
Werkzeugs gelitten hat, ist nur bei wenigen Menschen möglich. Nur wenige
sind in der Lage, den Aufwand zur Aneignung neuen Wissens objektiv in
Relation zu einer Produktivitäts- oder Qualitätssteigerung zu setzen.
Wilhelm M. schrieb:> Nur wenige> sind in der Lage, den Aufwand zur Aneignung neuen Wissens objektiv in> Relation zu einer Produktivitäts- oder Qualitätssteigerung zu setzen.
Oft ergibt sich eine Steigerung aber erst, wenn die Projektgröße und
Projektanzahl einen bestimmten Punkt überschreitet.
Bei kleinen Projekten kann ein zu hoher Abstraktionslevel eher
hinderlich sein.
Peter D. schrieb:> Wilhelm M. schrieb:>> Nur wenige>> sind in der Lage, den Aufwand zur Aneignung neuen Wissens objektiv in>> Relation zu einer Produktivitäts- oder Qualitätssteigerung zu setzen.>> Oft ergibt sich eine Steigerung aber erst, wenn die Projektgröße und> Projektanzahl einen bestimmten Punkt überschreitet.
Bei der Thematik, über die wir hier reden, hätte es sich ja schon längst
gelohnt, denn Du hast ja schon wohl länger über eine Lösung per CPP
nachgedacht.
Interessant, dass es nach all diesen zahllosen Jahren immer noch Leute
gibt, die mit cyblord diskutieren :)
Der Forentroll des Jahrhunderts: Miesepetrig, schlecht informiert und
niemals einsichtig.
honest_joe schrieb:> Interessant, dass es nach all diesen zahllosen Jahren immer noch Leute> gibt, die mit cyblord diskutieren :)> Der Forentroll des Jahrhunderts: Miesepetrig, schlecht informiert und> niemals einsichtig.
Miesepetrig: Definitiv.
Schlecht informiert würde ich nicht sagen. Ahnung hat er schon.
Wilhelm M. schrieb:> denn Du hast ja schon wohl länger über eine Lösung per CPP> nachgedacht.
Nur bin ich bisher nichtmal ansatzweise zu einem Ergebnis gekommen.
Peter D. schrieb:> In einem älteren Projekt hatte ich das so gemacht und dann per Define> ausgewählt, welches ich verwende. So richtig hat mich das aber auch> nicht vom Hocker gehauen.> Ich hätte gern, daß ich nur ein putchar() habe und dann mit "use UART0"> auswählen kann, welcher Port benutzt wird.
Mmh, ich bin mir nicht sicher, was Du eigentlich möchtest und wo das
problem dazu liegt?
Eine Funktion putchar, die aus verschiedenen Modueln aufgerufen wird und
mal an UART0 und mal an UART1 hängt? Wenn Du mit Funktionspointern
arbeitest ist das doch nur ein umkopieren. Und ohne ein if oder ein
switch.
Oder verschieden Putchars, für jedes Modul eines? Da wird einfach beim
Init des Moduls die zugehörige Sio mit übergeben und vom Modul selbst
(mit Baudrate etc.) initialisiert.
Der Vorteil von C++ liegt vor allem in virtuellen Objekten. Mit realen
Objekten (Uarts, Sensoren für einen bestimmten Messwert, etc) wird der
Vorteil immer kleiner. Und bei realen Objekten kann man OOP-Kapselung
durch automatisch kopierten Code sehr gut realisieren, da der
this-Pointer bei den Funktionen entfallen kann (Der Kontext ist ja im
kopierten Modul). Sobald das alles in einer Lib liegt, sorgt der Linker
auch für die je Projekt minimale Speicherbelegung.
C (statt C++ mit Templates und new) ermöglicht eine sehr tiefe statische
Analyse. Und der ganze Quatsch mit Namespaces und ADL, ... ja super, am
Ende läuft es doch darauf hinaus, dass alles voll qualifiziert werden
muss, sobald es ein wenig komplexer wird.
Wenn Du ein konkretes Problem in dem Bereich kreierst, bin ich sicher
(um eine Kiste Bier), dass ich in C einen genauso sauberen Ansatz
(Lesbarkeit, Codegröße, Performance) hinbekomme wie C++ Gurus hier.
Achim S. schrieb:>> Der Vorteil von C++ liegt vor allem in virtuellen Objekten.
Was sind denn in Deiner Nomenklatur "virtuelle Objekte" ...
> Mit realen> Objekten (Uarts, Sensoren für einen bestimmten Messwert, etc)
... und was sind "reale Objekte"?
Du meinst wahrscheinlich: Sensoren für Messgrößen, was der Sensor
liefert ist dann der Messwert.
> C (statt C++ mit Templates und new)
Was hat nun wieder template-Mechanik mit dyn. Allokation zu tun?
> ermöglicht eine sehr tiefe statische> Analyse. Und der ganze Quatsch mit Namespaces und ADL, ...
Und warum sind namespaces Quatsch?
Liest sich so wie gerührt und geschüttelt.
Wilhelm M. schrieb:> Was sind denn in Deiner Nomenklatur "virtuelle Objekte" ...
Objekte, die keinen festen, physischen Objekten entsprechen (oder
zugehörig sind), in die das System eingebettet ist. Beispiel Auto:
* Das Rad vorne links ist ein reales Objekt. Auch wenn es viele
Eigenschaften mit dem Rad vorne rechts gemein hat, gibt es ähnlich viele
konkrete Eigenschaften, die konkret behandelt werden müssen. Hier ist
der Vorteil von C++ vs. C gering. Es gibt wenig sinnvolle Codeteile, die
auch ein 6tes Rad händeln.
* Die konkrete CD im Player, eine Straßen im Navi sind virtuelle
Objekte. Es wird nur ein abstrahiertes Modell der Objekte verwendet,
Straßenbelag und Interpret werden vielleicht angezeigt, spielen aber
keine Rolle. Dem Code ist es egal, ob die Karte 20000 Straßen mehr hat
oder sich die Abspielposition der 70ten CD merkt.
Wilhelm M. schrieb:> Du meinst wahrscheinlich: Sensoren für Messgrößen, was der Sensor> liefert ist dann der Messwert
Hier ist die Unterscheidung: Im Auto ist der Radsensor v.l. ein
konkreter, "realer" Sensor. In einer Visualisierung oder einem
Diagnoseprogramm, wo z.B. alle Sensoren aufgelistet werden, ist er ein
rein virtuelles Objekt, z.B. mit dem Text-Attribut "Drehzahl Rad VL".
Wilhelm M. schrieb:> Und warum sind namespaces Quatsch?
Weil Du bei größeren Projekte feststellst, dass manche Dinge nicht so
sind, wie sie scheinen und beabsichtigt waren (durch ADL&co)
Wilhelm M. schrieb:> Was hat nun wieder template-Mechanik mit dyn. Allokation zu tun?
nichts. Warum? Es ging darum, dass dyn. Allokation die statische Analyse
des Speicherbedarfs erschweren kann.
Gruß
achim
Achim S. schrieb:> Eine Funktion putchar, die aus verschiedenen Modueln aufgerufen wird und> mal an UART0 und mal an UART1 hängt?
Nein.
Es soll eine Funktion sein, wo ich zur Compilezeit einfach zuweisen
kann, welche HW-Ressourcen sie belegt.
Achim S. schrieb:> Wilhelm M. schrieb:>> Was sind denn in Deiner Nomenklatur "virtuelle Objekte" ...> * Die konkrete CD im Player, eine Straßen im Navi sind virtuelle> Objekte.
Sag doch einfach, die Straße ist eine (konkrete / generische) Klasse,
die Exemplare davon sind Objekte. Das ist die "offizielle" Nomenklatur
in OOD/OOP. "Virtuelle Objekte" ist Dein persönlicher Sprachgebrauch.
Daneben gibt es technische Klassen, die keine Entsprechung in der realen
Welt haben.
> Wilhelm M. schrieb:>> Du meinst wahrscheinlich: Sensoren für Messgrößen, was der Sensor>> liefert ist dann der Messwert>> Hier ist die Unterscheidung: Im Auto ist der Radsensor v.l. ein> konkreter, "realer" Sensor. In einer Visualisierung oder einem> Diagnoseprogramm, wo z.B. alle Sensoren aufgelistet werden, ist er ein> rein virtuelles Objekt, z.B. mit dem Text-Attribut "Drehzahl Rad VL".
Mir ging es um die Unterscheidung: Messwert vs. Messgröße.
> Wilhelm M. schrieb:>> Und warum sind namespaces Quatsch?>> Weil Du bei größeren Projekte feststellst, dass manche Dinge nicht so> sind, wie sie scheinen und beabsichtigt waren (durch ADL&co)
Und wie wäre es ohne namespaces?
> Wilhelm M. schrieb:>> Was hat nun wieder template-Mechanik mit dyn. Allokation zu tun?>> nichts. Warum?
Weil Du es in einen Zusammenhang gestellt hast.
> Es ging darum, dass dyn. Allokation die statische Analyse> des Speicherbedarfs erschweren kann.
Aber templates ja wiederum nicht.
Du bist in Deinen Begriffen und Aussagen sehr unscharf.
Peter D. schrieb:> Achim S. schrieb:>> Eine Funktion putchar, die aus verschiedenen Modueln aufgerufen wird und>> mal an UART0 und mal an UART1 hängt?>> Nein.> Es soll eine Funktion sein, wo ich zur Compilezeit einfach zuweisen> kann, welche HW-Ressourcen sie belegt.
Bei C kann/will ich Dir nicht helfen, ein/zwei C++-Möglichkeiten habe
ich Dir oben aufgezeigt.
Peter D. schrieb:> Es soll eine Funktion sein, wo ich zur Compilezeit einfach zuweisen> kann, welche HW-Ressourcen sie belegt.
Da Du kein #defines möchtest, nehme ich an, dass Dein putchar kompiliert
in einer LIB liegt.
Mit Funktionpointern im einfachsten Fall:
1
/* in Headern */
2
structsUart
3
{
4
voidInit(intBaudrate);
5
voidput(intc);
6
void(*recv)(intc);
7
...
8
};
9
10
externstructsUartUart0;
11
externstructsUartUart1;
12
13
/* putchar in lib */
14
externintputchar(intc);
15
/* Uart to use with putchar. */
16
extern(const)structsUart*StdoutUart;
17
18
/* in main.c: variante 1, putchar(Firmware) initialisiert selber */