Forum: Mikrocontroller und Digitale Elektronik Hilfe bei Char[] und Verarbeitung


von Bernd (Gast)


Lesenswert?

Hallo Leute, ich schon wieder..

Habe noch eine Verständnis Frage zu Char..

Von der Quelle 
https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Der_UART
habe ich den Teil von ISR(USART_RXC_vect) kopier und angepast auf meine 
µC

meine PC schintstelle sende immer mit 4s Pause
1
Hallo Welt
2
#aabcdfe
3
#aAcdfe

jetzt dachte ich mir, mit folgend Code könnte ich das Empfange auswerten
1
while (1)
2
  {
3
    if (uart_str_complete == 1)
4
    {
5
      PORTB |= (1<<5); //debug
6
      uart_puts("\r\nEmpfang: >");
7
      uart_puts(uart_string);
8
      uart_puts("<\r\nPos 0: >");
9
      uart_puts(uart_string[0]);
10
      uart_puts("\r\nPos 1: >");
11
      uart_puts(uart_string[1]);
12
      uart_puts("<\r\n");
13
      _delay_ms(100);
14
      uart_str_complete = 0;
15
    }
16
    if (uart_str_complete == 0)
17
    {
18
      PORTB &= ~(1<<5); //debug
19
    } 
20
}

aber ich erhallte das..
1
Empfang: >Hallo Welt
2
Pos 2: >
3
Pos 3: ><
4
#aabcdfe
5
Empfang: >#aabcdfe<
6
Pos 2: >
7
Pos 3: ><
8
#aAcdfe
9
Empfang: >#aAcdfe<
10
Pos 2: >
11
Pos 3: ><
12
#aaB
13
Empfang: >#aaB<
14
Pos 2: >
15
Pos 3: ><

Warum, wo ist mein Denk Fehler...

Gruß

Bernd

von Nop (Gast)


Lesenswert?

Bernd schrieb:
> uart_puts(uart_string);
>       uart_puts("<\r\nPos 0: >");
>       uart_puts(uart_string[0]);

also das Erste, was mir da auffällt: Offenbar scheint uart_puts einen 
C-String zu erwarten. Das ist ein Pointer auf ein char-array, welches 
als letztes eine binäre Null enthält.

Andererseits ist uart_string[0] kein Pointer auf ein solches array, 
sondern enthält vielmehr einen einzelnen char. Bei einem vernünftigen 
Compiler sollte man hier eigentlich schon eine Warnung wegen 
inkompatibler Typen bekommen.

Ohne das Framework zu kennen, würde ich erwarten, daß eine Routine zum 
Ausgeben eines einzelnen Zeichens beispielsweise uart_putc heißt und 
nicht uart_puts.

von Jonas (Gast)


Lesenswert?

Servus,

probier mal:

char sendepuffer[20];
sprintf(sendepuffer,"\r\nEmpfang: >%c",uart_string);
uart_puts(sendepuffer);

Für den Fall eines C-Strings %s statt %c nehmen.

Gruß,
Jonas

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Passend zu uart_puts dürfte es auch eine Funktion uart_putc geben, die 
als Argument statt char* nur char erwartet.

von Nop (Gast)


Lesenswert?

Jonas schrieb:
> sprintf(sendepuffer,"\r\nEmpfang: >%c",uart_string);

Das nun gerade nicht, denn von printf und Konsorten sollte man embedded 
am besten die Finger lassen. Abgesehen davon ist uart_string ein Array, 
also ein String, den kann man nicht mit %c ausgeben. Das geht nur mit 
uart_string[0].

Wenn schon, dann eher sowas:
1
#define PUFFER_GROESSE 20
2
3
size_t laenge;
4
char sendepuffer[PUFFER_GROESSE];
5
6
strncpy(sendepuffer, "\r\nEmpfang: >", PUFFER_GROESSE);
7
sendepuffer[PUFFER_GROESSE-1] = 0;
8
laenge = strlen(sendepuffer);
9
10
if (laenge > PUFFER_GROESSE-2) /*buffer overflow verhindern*/
11
    laenge = PUFFER_GROESSE-2;
12
13
sendepuffer[laenge++] = uart_string[0];
14
sendepuffer[laenge] = 0;
15
16
uart_puts(sendepuffer);

Ist aber auch saumäßig umständlich und nur zu empfehlen, wenn es kein 
uart_putc() geben sollte und der Threadersteller auch keine solche 
Funktion schreiben möchte.

Zugegeben wäre es nicht ganz so umständlich, wenn man die defensive 
Programmierung mit Prüfung der Pufferlänge unterließe. ;-)

von S. R. (svenska)


Lesenswert?

Nop schrieb:
> Jonas schrieb:
>> sprintf(sendepuffer,"\r\nEmpfang: >%c",uart_string);
>
> Das nun gerade nicht, denn von printf und Konsorten sollte man embedded
> am besten die Finger lassen.

Weil?

Wenn genug Flash da ist, darf man sich auch ein bisschen Komfort gönnen, 
zum Debuggen sowieso. Und unter "Embedded" fällt auch ein Raspberry Pi 
3.

von Nop (Gast)


Lesenswert?

S. R. schrieb:
> Weil?

Speicherbedarf im Wesentlichen. Wenn man die Vollversion von printf 
nutzt, dann bindet man sich obendrein auch noch dynamisches 
Speichermanagement ans Bein. Das will man embedded auch möglichst 
vermeiden, u.a. wegen Speicherfragmentierung.

Auch zum Debuggen, weil das Timing dann u.U. völlig anders wird, 
zumindest sporadisch.

Zudem ist ein sprintf, nur um einen char an einen String anzuhängen, 
völliger Overkill.

> Und unter "Embedded" fällt auch ein Raspberry Pi 3.

Das Ausgangsposting verweist aber auf AVR, nicht auf Raspi 3.

von Bernd (Gast)


Lesenswert?

OMG danke klar.. Ist ja dann nur noch ein Char und kein Array mehr.
 Gruß

Bernd

von S. R. (svenska)


Lesenswert?

Nop schrieb:
> Wenn man die Vollversion von printf nutzt, dann bindet man sich
> obendrein auch noch dynamisches Speichermanagement ans Bein.

Allerdings gibt ein printf()-Aufruf den Speicher nach Benutzung auch 
vollständig wieder zurück...

> Das will man embedded auch möglichst
> vermeiden, u.a. wegen Speicherfragmentierung.

...womit keine Speicherfragmentierung entstehen kann (sofern das 
printf() nicht selbst unterbrochen wird und der unterbrechende Code 
selbst dynamischen Speicher nutzt). Notfalls wird dynamischer Speicher 
auf einem kleinen, statisch alloziierten Array implementiert, dann 
entfällt der Grund komplett.

> Auch zum Debuggen, weil das Timing dann u.U. völlig anders wird,
> zumindest sporadisch.

Wer aktiv Debugmeldungen ausgibt, verändert das Programm und damit das 
Timing ohnehin, ob da nun printf drin ist, ist auch egal. Im schlimmsten 
Fall wird der Programmablauf durchs Debuggen serialisiert und der 
eigentliche Bug verschwindet.

> Zudem ist ein sprintf, nur um einen char an einen String anzuhängen,
> völliger Overkill.

Das sollte ein moderner Compiler aber erkennen und optimieren können.

>> Und unter "Embedded" fällt auch ein Raspberry Pi 3.
> Das Ausgangsposting verweist aber auf AVR, nicht auf Raspi 3.

Du schriebst aber nicht AVR, sondern Embedded.

Was ich damit sagen will: Ja, printf ist (einmalig!) teuer, schenkt 
einem dafür aber ziemlich viel Komfort und macht den Code deutlich 
lesbarer als manuelle Stringoperationen. Das ist ein starker Grund, es 
trotzdem verfügbar zu halten, selbst wenn man es im Produktivcode eher 
vermeidet. Zumindest ich finde Jonas Dreizeiler wesentlich einfacher zu 
verstehen als deinen Krampf.

Muss man den (zu) kleinen AVR bis zum Rand vollstopfen, braucht die 
Anwendung selbst zur Laufzeit dynamischen Speicher oder gibt es im 
Timing keine Freiheiten mehr, sollte man printf besser vermeiden. Aber 
das ist jedem, der auf der Ebene arbeitet, auch klar.

von Nop (Gast)


Lesenswert?

S. R. schrieb:

> ...womit keine Speicherfragmentierung entstehen kann (sofern das
> printf() nicht selbst unterbrochen wird und der unterbrechende Code
> selbst dynamischen Speicher nutzt).

Dann muß man immer noch überhaupt erstmal einen Heap anlegen. Ich tue 
das gar nicht erst, weil: keine dynamische Speicherallozierung.

> Notfalls wird dynamischer Speicher
> auf einem kleinen, statisch alloziierten Array implementiert, dann
> entfällt der Grund komplett.

Fände ich zuviel Aufwand. Insbesondere, um einen Char auszugeben, wofür 
ich übrigens schlichtweg die putc-Routine nutzen würde, die ohnehin 
implementiert sein wird, weil puts schließlich zeichenweise genau darauf 
zurückgreift.

> Wer aktiv Debugmeldungen ausgibt, verändert das Programm und damit das
> Timing ohnehin, ob da nun printf drin ist, ist auch egal.

Das ist natürlich auch wieder wahr.

>> Zudem ist ein sprintf, nur um einen char an einen String anzuhängen,
>> völliger Overkill.
>
> Das sollte ein moderner Compiler aber erkennen und optimieren können.

Würde ich mich nicht drauf verlassen. Immerhin nehme ich C gerade 
deswegen, weil ich kontrollieren kann und will, was eigentlich abläuft.

> Du schriebst aber nicht AVR, sondern Embedded.

Ja, und ich kenne es bei allen ernsthaften Projekten, daß überhaupt gar 
nicht erst Code zur Dynamik dabei ist. Und deswegen wäre es einige 
Arbeit, das nur für Debugausgaben einzufügen.

Gut, wenn man einen Raspi nimmt, ist sowieso alles egal. Man kann auch 
einen kleinen NUC nehmen.

> Was ich damit sagen will: Ja, printf ist (einmalig!) teuer, schenkt
> einem dafür aber ziemlich viel Komfort und macht den Code deutlich
> lesbarer als manuelle Stringoperationen.

Dynamische Inhalte? Also hier wäre es ein Zeichen, dafür braucht man 
überhaupt keine String-Ops. Ansonsten, um z.B. Integer zu konvertieren, 
nutzt man üblicherweise entsprechend dedizierte, schlichte Funktionen.

> Zumindest ich finde Jonas Dreizeiler wesentlich einfacher zu
> verstehen als deinen Krampf.

Der ist deswegen schwerer lesbar, weil er nebenbei auch noch gegen 
Buffer-Overflows geschützt ist, was der reine sprintf-Aufruf auch nicht 
gewesen ist. Das ist insofern ein unfairer Vergleich.

von S. R. (svenska)


Lesenswert?

Nop schrieb:
> Dann muß man immer noch überhaupt erstmal einen Heap anlegen. Ich tue
> das gar nicht erst, weil: keine dynamische Speicherallozierung.

Muss man auf einem AVR nicht, macht die avr-libc für dich. Auf einem 
kleinen ARM lege ich aber immer einen Heap an und verknote 
stdin/stdout/stderr mit passenden Devices (Keypad/Display oder UART, je 
nach System).

Hat den Vorteil, dass ich Teile der Programmlogik einfach unter Linux 
entwickeln und auf dem Controller benutzen kann.

>> Das sollte ein moderner Compiler aber erkennen und optimieren können.
>
> Würde ich mich nicht drauf verlassen. Immerhin nehme ich C gerade
> deswegen, weil ich kontrollieren kann und will, was eigentlich abläuft.

Also ich benutze C, weil mir der Compiler viel Arbeit abnimmt. Wenn ich 
dem ständig hinterherräumen muss, bringt es nicht so viel. (Man sollte 
trotzdem drauf achten, dass er keinen groben Unfug treibt.)

Und in den meisten Fällen ist es egal, ob deine Debugausgabe nun 200 
oder 1000 Takte braucht, weil die CPU sowieso nichts zu tun hat.

> Ja, und ich kenne es bei allen ernsthaften Projekten, daß überhaupt gar
> nicht erst Code zur Dynamik dabei ist. Und deswegen wäre es einige
> Arbeit, das nur für Debugausgaben einzufügen.

Die Entscheidung, welcher Code nun in der ELF drin sein muss, trifft für 
mich der Linker. Wenn niemand dynamischen Speicher braucht, dann ist in 
der ELF kein malloc drin, fertig.

>> Was ich damit sagen will: Ja, printf ist (einmalig!) teuer, schenkt
>> einem dafür aber ziemlich viel Komfort und macht den Code deutlich
>> lesbarer als manuelle Stringoperationen.
>
> Dynamische Inhalte? Also hier wäre es ein Zeichen, dafür braucht man
> überhaupt keine String-Ops. Ansonsten, um z.B. Integer zu konvertieren,
> nutzt man üblicherweise entsprechend dedizierte, schlichte Funktionen.

Um eine Variable mit höherer Updaterate (> 2 Hz) zu verfolgen, ist es 
sinnvoll, wenn sie eine feste Position auf dem Terminal/Display hat. Ein
1
printf("A: %03d - B: %03d - C: %03d\n", sensor_a, sensor_b, sensor_c);
löst das Problem ganz wunderbar (und ja, meine Displaytreiber verstehen 
alle wenigstens \r, \n und \t).

>> Zumindest ich finde Jonas Dreizeiler wesentlich einfacher zu
>> verstehen als deinen Krampf.
>
> Der ist deswegen schwerer lesbar, weil er nebenbei auch noch gegen
> Buffer-Overflows geschützt ist, was der reine sprintf-Aufruf auch nicht
> gewesen ist. Das ist insofern ein unfairer Vergleich.

Stimmt, Jonas hätte snprintf statt sprintf benutzen sollen.
Dann hätte er 5 Zeichen mehr tippen müssen. Und der Vergleich wäre fair.

von Jonas (Gast)


Lesenswert?

Ich mach meine seriellen Ausgaben am AVR immer mit sprintf und hatte 
damit noch nie Probleme.

von Nop (Gast)


Lesenswert?

S. R. schrieb:
> Muss man auf einem AVR nicht, macht die avr-libc für dich.

Für Bastelprojekte kann man das sicherlich so machen. Bei ernsthafteren 
Projekten käme man damit nichtmal durchs Code Review. Viele Kunden 
fordern z.B. MISRA-Einhaltung. Aus gutem Grund, ich habe schon genug 
Projekte mit dynamischer Speicherverwaltung gesehen, die genau daran 
eingegangen sind. Deswegen verbannt man das kurzerhand komplett, und das 
Problem ist gelöst.

> Hat den Vorteil, dass ich Teile der Programmlogik einfach unter Linux
> entwickeln und auf dem Controller benutzen kann.

Dazu braucht man kein printf in den Teilen, die auf dem Controller 
laufen sollen. BTDT.

> Also ich benutze C, weil mir der Compiler viel Arbeit abnimmt. Wenn ich
> dem ständig hinterherräumen muss, bringt es nicht so viel.

Dann kannste auch gleich eine Sprache mit automatischem 
Speichermanagement nehmen. Die sind dafür gedacht und nehmen noch mehr 
Arbeit ab. Laufen u.U. auf einem µC dann halt nicht so toll.

> Die Entscheidung, welcher Code nun in der ELF drin sein muss, trifft für
> mich der Linker.

Ist bei ernsthafteren Projekten nicht möglich, weil Du dann dead code 
drin hast. Fliegt beim Code Review durch, spätestens beim coverage test. 
Bei Bastelprojekten hingegen ist sowieso alles egal, muß nur irgendwie 
halbwegs laufen.

> Um eine Variable mit höherer Updaterate (> 2 Hz) zu verfolgen, ist es
> sinnvoll, wenn sie eine feste Position auf dem Terminal/Display hat.

Auch dazu braucht man kein printf. BTDT. Etwas wie itoa zu schreiben 
(und zwar mit Buffercheck), das ist ja nun kein Hexenwerk.

> Stimmt, Jonas hätte snprintf statt sprintf benutzen sollen.
> Dann hätte er 5 Zeichen mehr tippen müssen. Und der Vergleich wäre fair.

Wer embedded mit printf arbeitet, nutzt ohnehin auch strcpy und frißt 
kleine Kinder. :-) Wie man übrigens gerade gesehen hat - wer mit printf 
arbeitet, kommt von selber nicht auf snprintf. Du ja auch erst nach 
meinem Hinweis.

von Peter D. (peda)


Lesenswert?

Jonas schrieb:
> Ich mach meine seriellen Ausgaben am AVR immer mit sprintf und hatte
> damit noch nie Probleme.

Sehe ich auch so.
Immer dieses völlig unbegründete float/printf Bashing.
Die Compiler und MCs sind doch nicht auf dem Stand von vor 20 Jahren 
stehen geblieben.

Ich benutze printf auf dem AVR, sogar für floats. Kostet einmalig ~4kB 
Flash. Malloc wird aber nicht eingebunden, wozu auch.

von S. R. (svenska)


Lesenswert?

Nop schrieb:
>> Also ich benutze C, weil mir der Compiler viel Arbeit abnimmt. Wenn ich
>> dem ständig hinterherräumen muss, bringt es nicht so viel.
> Dann kannste auch gleich eine Sprache mit automatischem
> Speichermanagement nehmen. Die sind dafür gedacht und nehmen noch mehr
> Arbeit ab. Laufen u.U. auf einem µC dann halt nicht so toll.

Wenn ich der Meinung bin, meine Programmiersprache sei so schlecht, dass 
ich ihr ständig hinterherräumen muss - sie also von Grund auf 
unzuverlässig ist - dann mache ich was falsch und sollte mich selbst 
hinterfragen.

Ich vertraue modernen Compilern zu einem relativ hohen Anteil und stehe 
damit offensichtlich nicht alleine da.

Nop schrieb:
>> Die Entscheidung, welcher Code nun in der ELF drin sein muss, trifft für
>> mich der Linker.
> Ist bei ernsthafteren Projekten nicht möglich, weil Du dann dead code
> drin hast.

Ich wusste garnicht, dass parametrierbarer Code verboten ist (Faktoren 
von 0 oder 1 ergeben immer toten Code). Im Gegenteil, ich bin mir sogar 
relativ sicher, dass man innerhalb einer Codebasis gewisse 
Funktionalität halten darf, die nicht in jedem Produkt vorhanden ist.

von Nop (Gast)


Lesenswert?

S. R. schrieb:

> Ich vertraue modernen Compilern zu einem relativ hohen Anteil und stehe
> damit offensichtlich nicht alleine da.

Naja, vielleicht kannste ja mit nem Tip von nem Profi was anfangen:
http://embeddedgurus.com/stack-overflow/tag/printf/

> Im Gegenteil, ich bin mir sogar
> relativ sicher, dass man innerhalb einer Codebasis gewisse
> Funktionalität halten darf, die nicht in jedem Produkt vorhanden ist

Dann sei Dir mal da ganz sicher. So sicher, wie Du sein kannst, ohne 
Erfahrung mit entsprechend regulierten, ernsthaften Produkten zu haben. 
So sicher, wie ein Bastler nur sein kann.

von S. R. (svenska)


Lesenswert?

Nop schrieb:
> Dann sei Dir mal da ganz sicher. So sicher, wie Du sein kannst, ohne
> Erfahrung mit entsprechend regulierten, ernsthaften Produkten zu haben.

Willst du damit sagen, dass ein Hersteller von z.B. "Motorsteuergerät X" 
für zwei Produkte "mit Funktion Y" und "mit Funktion Z" zwei völlig 
getrennte Codebasen bereithalten muss, die dann zueinander aus 80% 
Copy/Paste bestehen?

Bei uns laufen einige Projekte (im Bereich des automatisierten Testens), 
die auf Produktlinienmanagement (also gemeinsame Codebasis mit 
verschiedenen Funktionalitäten) abzielen, mit Blick auf Automotive.

Und nur um mal willkürlich ein Beispiel rauszunehmen: AVM könnte nie ein 
Sicherheitsupdate für mehrere Generationen von Fritzboxen anbieten, wenn 
sie nicht eine gemeinsame (zumindest pro Hardware-Generation) Codebasis 
hätten.

Davon mal abgesehen bin ich der festen Überzeugung, dass die meisten 
hier diskutierten Projekte nicht in der Welt solcher "entsprechend 
regulierten, ernsthaften Produkte" liegt, und dann sind float, printf 
und malloc - wenn vernünftig eingesetzt - auch auf einem AVR sinnvoll.

Mache mal Butter bei die Fische und gib mir Grund, meine Weltanschauung 
zu ändern. Denn wie du richtig erfasst, lebe ich nicht in einer 
MISRA-regulierten Welt.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Nop schrieb:
> Naja, vielleicht kannste ja mit nem Tip von nem Profi was anfangen:
> http://embeddedgurus.com/stack-overflow/tag/printf/

Dann lies den aber auch bis zum Ende durch.

> Now do the above mean you should always eschew formatted
> output functions?
> No! Indeed I recommend the use of vsprintf() here for certain
> classes of problem.
> What I do recommend is that you think long and hard before
> using these functions to ensure that you really understand
> what you are doing (and getting) when you use them.

von Nop (Gast)


Lesenswert?

Rufus Τ. F. zitierte im Beitrag #4746179:
>> What I do recommend is that you think long and hard before
>> using these functions

Und nach gar nicht mal SO viel hartem Nachdenken kommt man unweigerlich 
dazu, daß printf für die Ausgabe eines characters auf der Seriellen 
einfach Quatsch ist.

von Nop (Gast)


Lesenswert?

S. R. schrieb:
> Willst du damit sagen, dass ein Hersteller von z.B. "Motorsteuergerät X"
> für zwei Produkte "mit Funktion Y" und "mit Funktion Z" zwei völlig
> getrennte Codebasen bereithalten muss, die dann zueinander aus 80%
> Copy/Paste bestehen?

Das ist der einfachste Weg. Man kann natürlich auch sein Repo aufspalten 
in einen "common part", der bei beiden identisch ist, und in einen 
"project part", wo die unterschiedlichen 20% drinstehen. Das macht man 
üblicherweise, wenn man viele nur leicht unterschiedliche Produkte 
anbietet.

Das ist dann kein leerer Code, weil diese 80% in beiden Varianten voll 
genutzt werden.

Wobei das dann ziemlich viel Papierkrieg wird, wenn sich im common part 
etwas ändert, weil aus Projekt Y etwas eingeflossen ist, was auch 
Projekt Z betrifft - da muß dann der Dokuprozeß schon wirklich gut 
aufgesetzt sein.

> Und nur um mal willkürlich ein Beispiel rauszunehmen: AVM könnte nie ein
> Sicherheitsupdate für mehrere Generationen von Fritzboxen anbieten, wenn
> sie nicht eine gemeinsame (zumindest pro Hardware-Generation) Codebasis
> hätten.

AVM stellt auch Produkte her, bei denen keine besondere Abnahme 
gefordert ist. Geht auch nicht, wenn man Linux verwendet, weil das 
einfach viel zu komplex ist, als daß man da jemand die Hausmarke von 90% 
code coverage beim testing knacken könnte.

Die übrigen 10% sind übrigens bei ernsthaften Projekten zu begründen, 
und zwar jeder beim Test nicht durchlaufene Zweig einzeln. Mitsamt 
Analyse, inwieweit der problematisch werden könnte. Und das ist noch die 
harmlose Variante von coverage, weil das ja nur die Verzweigung erfaßt.

> Davon mal abgesehen bin ich der festen Überzeugung, dass die meisten
> hier diskutierten Projekte nicht in der Welt solcher "entsprechend
> regulierten, ernsthaften Produkte" liegt

Ich halte es aber für eine gute Praktik, bei Hobbyprojekten höhere 
Standards anzusetzen, gerade weil man dabei ja auch was lernen will. 
Zudem laufen sie dann auch einfach solider, und man spart sich Debugzeit 
gerade bei den ekligen Fehlern, die nur manchmal auftreten.

Es hat ja seinen Grund, wieso gewisse Praktiken bei ernsthaften 
Projekten nicht erlaubt sind. Natürlich ist es etwas anderes, ob die 
Fahrzeugsteuerung hingepfuscht wird und dann Leute sterben wie im Falle 
von Toyota, oder ob halt ein AVR-kontrollierter LED-Sternenhimmel an der 
Zimmerdecke mal nicht geht.

>, und dann sind float, printf
> und malloc - wenn vernünftig eingesetzt - auch auf einem AVR sinnvoll.

Für Bastelzwecke, oder wenn bloß Komfort bei Endverbraucherprodukten auf 
dem Spiel steht, ist es ohnehin völlig gleichgültig, da kann man 
abliefern, was immer man will. Endverbraucher kaufen ja auch den letzten 
Schrott, Hauptsache 10 Euro billiger. Das kann man dann wirtschaftlich 
letztlich nur noch schaffen, indem man vorgefertigte Bibliotheken 
irgendwie zusammenbrät und ein bißchen mal durchprobiert, ob's läuft. 
Jegliche Entwicklungszeit da reinzustecken würde das Projekt zum 
Minusgeschäft machen.

Ich habe übrigens auch bereits Projekte erlebt, die nicht reguliert 
waren, und wo ausgiebig mit malloc gearbeitet wurde. Mein Widerstand 
wurde ignoriert, weil ich seinerzeit ja erst zwei Jahre Berufserfahrung 
hatte, und was weiß ein Neuling schon.

Weil man damit das Gerät je nach Umständen dynamisch handhaben konnte, 
was bei der BOM einen Speicherriegel gespart hat. Das Ende vom Lied war 
dann, daß man dem Ding einen wöchentlichen Autoreset verpaßt hat, anders 
war das nicht mehr in den Griff zu kriegen.

Auch das eine typische Folge dynamischer Speicherverwaltung - denn der 
Zustand des Systems hängt dann davon ab, was es vorher getan hat. Der 
Knackpunkt dabei ist - das System ist nicht mehr testbar.

Übrigens ist das Argument "der Compiler macht das schon" in solchen 
Kreisen aus gutem Grund ebenfalls verpönt. Das hat mit -O0 zu laufen, 
sonst muß stärkere Hardware genommen werden. Hintergrund ist z.B. die 
völlig hirntote Algorithmik zum automatischen Inlining beispielsweise 
beim GCC, die einen gerne mal einen stack overflow beschert, weil der 
Compiler es eben nicht besser weiß.

Natürlich macht man das als Hobbyist nicht so, man will schließlich 
maximalen Wums aus dem kleinen Ding zaubern, was man da vor sich hat. 
Aber die Problematik sollte man schon verstehen, und das fängt damit an, 
daß man seinem Compiler mißtraut. Nicht ganz so sehr wie sich selber 
natürlich, aber doch schon.

von Nop (Gast)


Lesenswert?

Nachtrag: Wenn ich Dinge habe, die jeweils viel Speicher brauchen, die 
ich aber nie zugleich mache, dann nehme ich nicht dynamische 
Allozierung, sondern lege das auf den Stack, und zwar in Funktionen, die 
nicht im selben call tree liegen. Weswegen ich auch so allergisch gegen 
das auto-inlining beim GCC bin und ihn an solchen Stellen mit 
haufenweise no-inline-Funktionsattributen im Zaum halte.

Erstens fragmentiert der Stack garantiert nicht, zweitens gibt es keine 
Allokationsfehler, drittens muß ich das nicht manuell freigeben, 
viertens habe ich keinen Allokator mit gelegentlich irrwitzigen Zeiten 
drin, und zuguterletzt kann ich über den call tree den maximalen 
Stackverbrauch leicht ausrechnen. Dadrauf kommt dann noch der 
Stackbedarf aller Interrupts zusammengenommen sowie ein bißchen Reserve.

Größere Batzen, die aber permanent gebraucht werden, gibt's dann halt 
als globale Variable, nach Möglichkeit mit reduziertem Scope.

Am Ende muß man sich nur das Mapfile und den Calltree anschauen, und man 
hat keinen Speicherüberlauf mehr und muß sich nie mit Fehlerbehandlung 
für malloc herumschlagen. Nicht nur, daß ich das aus ernsthafteren 
Projekten so kenne, es funktioniert auch im Hobby angenehm robust. Spart 
außerdem einiges an Debugzeit für schwer nachverfolgbare Fehler.

Was wohl der Grund ist, wieso sich Profis irgendwann mal hingesetzt 
haben und das beschlossen haben.

von S. R. (svenska)


Lesenswert?

Du verbietest also jegliche Form von dynamischem Speicher und möglichst 
jeden Komfort, um zertifizierbaren und TÜV-prüfbaren Code zu haben. Mag 
in deinem Job sinnvoll sein, für Hobbyprojekte ist es das in deiner 
Absolutheit aus meiner Sicht nicht.

Ich werde auch weiterhin -Os (oder -O3, je nach Bedarf), printf und 
float benutzen. Und das auch jedem raten, der davon profitieren kann und 
nicht zufällig Autopiloten für Flugzeuge schreibt oder schlicht keinen 
Platz dafür hat.

Nop schrieb:
> Weil man damit das Gerät je nach Umständen dynamisch handhaben konnte,
> was bei der BOM einen Speicherriegel gespart hat. Das Ende vom Lied war
> dann, daß man dem Ding einen wöchentlichen Autoreset verpaßt hat, anders
> war das nicht mehr in den Griff zu kriegen.

Ich kenne die Variante, dass in ein Produkt ein abgekündigter 
RAM-Baustein eindesigned wurde - und als der nicht mehr lieferbar war, 
gab es nur noch einen halb so großen Baustein (16 statt 32 MB). Dem 
Linux wurde am Ende ein zram als swap gegeben, dann lief die Anwendung 
wieder.

> Übrigens ist das Argument "der Compiler macht das schon" in solchen
> Kreisen aus gutem Grund ebenfalls verpönt. Das hat mit -O0 zu laufen,
> sonst muß stärkere Hardware genommen werden.

Was bin ich froh, dass ich in solchen Kreisen nicht unterwegs sein muss. 
Da muss meine CPU nicht mit hirntotem Code sinnlos beschäftigt werden, 
sondern die restlichen Takte im Leerlauf verbringen (und Strom sparen).

Wenn ihr den Compiler quasi abschaltet, warum nutzt ihr dann überhaupt 
einen?

> Aber die Problematik sollte man schon verstehen, und das fängt damit an,
> daß man seinem Compiler mißtraut. Nicht ganz so sehr wie sich selber
> natürlich, aber doch schon.

Ich misstraue lieber den Bibliotheken, die ich einbinde. Die sind meist 
von deutlich schlechterer Qualität als der Compiler selbst.

Aber ja, man sollte wissen, was man tut. Daher nutze ich auf den 
kleinsten Systemen malloc auch nur für die Initialisierung (oder 
printf).

von Rolf M. (rmagnus)


Lesenswert?

Nop schrieb:
> Für Bastelprojekte kann man das sicherlich so machen. Bei ernsthafteren
> Projekten käme man damit nichtmal durchs Code Review. Viele Kunden
> fordern z.B. MISRA-Einhaltung. Aus gutem Grund, ich habe schon genug
> Projekte mit dynamischer Speicherverwaltung gesehen, die genau daran
> eingegangen sind. Deswegen verbannt man das kurzerhand komplett, und das
> Problem ist gelöst.

Das ist der Grund, warum ich so Dinge wie MISRA nicht mag. Dinge, die 
von Anfängern oft falsch benutzt werden, werden einfach komplett 
verbannt, was dann die Leute, die über das Anfänger-Stadium hinaus 
gewachsen sind, in ihrer Arbeit behindert. Meiner Meinung nach muss und 
kann man gar nicht mit ein paar Regeln dafür sorgen, dass ein Anfänger 
fehlerfreien sicherheitskritschen Code schreibt. Wer's kann, braucht 
kein MISRA, um es richtig zu machen, wer es nicht kann, wird's auch mit 
MISRA nicht hinbekommen. Oder anders: MISRA macht auch schlechten 
Programmieren keine guten Programmierer.

von Nop (Gast)


Lesenswert?

Rolf M. schrieb:
> Das ist der Grund, warum ich so Dinge wie MISRA nicht mag. Dinge, die
> von Anfängern oft falsch benutzt werden, werden einfach komplett
> verbannt

Was so nicht stimmt. Hast Du Dir denn MISRA mal genauer durchgelesen?

Man kann durchaus gegen die Regeln verstoßen. Allerdings braucht man 
dann einen definierten Prozeß, wo man dokumentiert, daß man eine 
MISRA-Regel mißachtet hat, wieso das technisch nicht anders möglich war, 
und eine Analyse der Konsequenzen - speziell, daß es keine negativen 
gibt. Das muß dann vom Entwickler abgezeichnet werden, bei den 
heftigeren Regeln auch von dessen Vorgesetzten.

Die Konsequenz ist, daß man nicht einfach "mal eben so" dagegen 
verstößt, sondern gezwungen wird, gründlich nachzudenken. Die natürliche 
Faulheit des Entwicklers führt von selber dazu, daß man sich diesen 
Aufwand nur gibt, wenn es wirklich notwendig ist. Und nicht, weil es 
bequem ist - der Zweck ist gerade, bequeme, aber in der Praxis 
statistisch gesehen bedenkliche Konstrukte unbequem zu machen.

von Nop (Gast)


Lesenswert?

S. R. schrieb:

> Du verbietest also jegliche Form von dynamischem Speicher und
> möglichst jeden Komfort, um zertifizierbaren und TÜV-prüfbaren
> Code zu haben.

Nein, sondern ich meide das, weil das aus gutem Grund in zertifizierten 
Produkten nicht erlaubt ist. Zwar gebe ich Dir recht, daß das für 
Hobbyprojekte auch nicht nötig ist. Dennoch möchte ich bei meinem 
Hobbyprojekten, daß sie zuverlässig laufen.

Was übrigens auch der Grund ist, wieso ich auch privat Tools zur 
statischen Code-Analyse einsetze.

> Ich werde auch weiterhin -Os (oder -O3, je nach Bedarf)

Privat nutze ich auch meistens -O2 und an Hotspots per Pragma -O3. Nur 
muß man sich dann eben auch damit befassen, wie man dann z.B. den 
Sanitiser benutzen kann, um diverse Arten von undefiniertem Verhalten 
aufzudecken, von dem nach der Codierphase in jedem nicht-trivialen 
C-Programm mit hoher Sicherheit was drinsteckt.

> float benutzen.

Float ist ja an sich auch unbedenklich. Wenn man eben mit Dingen 
hantiert, wo Integer einfach nicht so geeignet sind, dann hat niemand 
was gegen float. Im Gegenteil, das wird ja z.B. auf Cortex-M4 sogar von 
der FPU unterstützt.

Wobei man die üblichen Fallen bei float natürlich schon kennen sollte. 
Nie Vergleiche auf Identität, sondern nur >=, <=, >, <. Und keine 
Schleifen mit Schrittweite von 0.1f, habe ich auch schon gesehen. Kein 
Wunder, daß das bei MISRA ausdrücklich gelistet ist.

> Ich kenne die Variante, dass in ein Produkt ein abgekündigter
> RAM-Baustein eindesigned wurde

Uhh, fiese Falle, ja.

> Wenn ihr den Compiler quasi abschaltet

Den Optimierer, nicht den Compiler. Der Compiler setzt ja nach wie vor 
C-Code in Maschinencode um. Ich habe auch ganze Projekte schon in 
Assembler gehabt, aber die Entwicklungszeiten sind deutlich länger, und 
die Fehleranfälligkeit auch. In C hat man ein laxes Typensystem, in 
Assembler gar keines mehr.

> Aber ja, man sollte wissen, was man tut. Daher nutze ich auf den
> kleinsten Systemen malloc auch nur für die Initialisierung (oder
> printf).

Für die Init-Phase, also quasi-statisch, geht das ja auch. Ist dann 
Geschmackssache, ob man nicht gleich ein statisches Array dafür anlegt.

von Rolf M. (rmagnus)


Lesenswert?

Nop schrieb:
> Rolf M. schrieb:
>> Das ist der Grund, warum ich so Dinge wie MISRA nicht mag. Dinge, die
>> von Anfängern oft falsch benutzt werden, werden einfach komplett
>> verbannt
>
> Was so nicht stimmt. Hast Du Dir denn MISRA mal genauer durchgelesen?
>
> Man kann durchaus gegen die Regeln verstoßen. Allerdings braucht man
> dann einen definierten Prozeß, wo man dokumentiert, daß man eine
> MISRA-Regel mißachtet hat, wieso das technisch nicht anders möglich war,

Und genau das ist der Punkt, bei dem es einen behindert. Man kann 
bestimmte Konstrukte nur noch nutzen, wenn es technisch nicht anders 
geht, und nicht mehr, wenn sie einfacher oder praktischer sind als die 
Alternative.
Viele Regeln verbieten Konstrukte, die häufig missbraucht werden. Die 
können aber bei bestimmungsgemäßem Gebrauch die Codequalität auch 
verbessern. MISRA unterbindet das aber auch.

> Die natürliche Faulheit des Entwicklers führt von selber dazu, daß man
> sich diesen Aufwand nur gibt, wenn es wirklich notwendig ist.

Eben. Man macht einfach nutzbare Dinge so umständlich, dass sie faktisch 
nicht mehr nutzbar sind.

> Und nicht, weil es bequem ist - der Zweck ist gerade, bequeme, aber in
> der Praxis statistisch gesehen bedenkliche Konstrukte unbequem zu machen.

"statistisch gesehen bedenkliche Konstruke"... interessant formuliert.
Ich halte es für sinnvoller, Leuten beizubringen, ihr Werkzeug wirklich 
zu verstehen, statt dessen Funktionalität künstlich zu beschneiden, 
damit auch welche, die es nicht verstehen, bestimmte Fehler nicht machen 
können.

Nop schrieb:
> Nein, sondern ich meide das, weil das aus gutem Grund in zertifizierten
> Produkten nicht erlaubt ist. Zwar gebe ich Dir recht, daß das für
> Hobbyprojekte auch nicht nötig ist. Dennoch möchte ich bei meinem
> Hobbyprojekten, daß sie zuverlässig laufen.

Dynamischer Speicher macht nicht automatisch ein Programm unzuverlässig. 
Wobei man in reinem C mangels jeglicher Automatismen tatsächlich sehr 
aufpassen muss. Ich meide es da auch, wenn möglich.

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

Rolf M. schrieb:

> Eben. Man macht einfach nutzbare Dinge so umständlich, dass sie faktisch
> nicht mehr nutzbar sind.

Genau das ist die Absicht, weil die Praxis gezeigt hat, daß die Fallen 
dabei eben immer auf SCHNAPP gestellt sind. Und jeder Produktfehler 
letztlich sehr, sehr teuer werden kann. Besonders, wenn sich daran auch 
noch vier Wochen Testfeld anhängen, und für eine Vollabnahme gibt sich 
ein Kunde da nicht mit inkrementellen Delta-Tests zufrieden.

> Ich halte es für sinnvoller, Leuten beizubringen, ihr Werkzeug wirklich
> zu verstehen, statt dessen Funktionalität künstlich zu beschneiden,
> damit auch welche, die es nicht verstehen, bestimmte Fehler nicht machen
> können.

Die Praxis zeigt, daß dem nunmal nicht so ist. Weil die Leute, die hier 
Software schreiben, in erster Linie mal nicht Programmierer sind, 
sondern eher E-Techniker. Weil man auch noch ein gehörig Maß an Wissen 
um die Hardware mitbringen muß. Deren Wissen besteht nicht so sehr 
darin, sich um die Fiesheiten von C Gedanken zu machen, als im 
Systemverständnis.

Versuch denen mal begreiflich zu machen, daß ein Bitfield sich für 
Registermapping oder Messagemapping sich so lecker anzubieten scheint, 
man aber den Köder trotzdem nicht schlucken darf, weil man sich dann in 
einem riesigen Wolleknäuel wiederfindet. Mit Krabben zwischendrin.

Ich habe ja meine ausgeprägte Haßliebe zu C, aber im Grunde finde ich es 
den genialsten portablen Makro-Assembler überhaupt. Nur, ich beschäftige 
mich aus Interesse auch damit. Die meisten Leute tun das aber nicht, 
schon gar nicht in der Freizeit. Da C Dir als Programmierer das gibt, 
was Du sagst (und nicht das, was Du willst), ist das keine besonders 
gute Kombination mit Leuten, die im Grunde bei Pascal stehengeblieben 
sind (btw. eine sehr gute Lehrsprache).

Und das wird alles immer schlimmer, seitdem man an den Unis auch noch 
auf Java umstellt und die Leute mit Pointerarithmetik und mehrfach 
dereferenzierten Pointern ebensowenig in Berührung gekommen sind wie mit 
den Tücken realer Rekursion.

Spolsky schrieb dazu schon vor Jahren:
http://www.joelonsoftware.com/articles/theperilsofjavaschools.html

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
Noch kein Account? Hier anmelden.