Forum: Compiler & IDEs Was passiert, wenn man als Funktionsparameter statt einem prog_char* ein char* übergibt?


von Karl (Gast)


Lesenswert?

Guten Abend,

im AVR Beispielcode für Elm Chan FatFS gibt es die Dateien xitoa.s und 
xitoa.h.
Die werden dafür benutzt, um Daten über Uart zu schicken. Darin gibt es 
unter Anderem die Funktion:
void xprintf(const prog_char *format, ...);  /* Send formatted string to 
the registered device */

Im Beispiel übergibt er den Formatstring mit PSTR(), was den String 
irgendwo in den PROGMEM schreibt.

Wenn man jetzt versucht, dort einen char* String zu übergeben, der nicht 
explizit im Flash gespeichert wurde, kommt bei mir in der Ausgabe nicht 
das raus, was im String steht, sondern eine Variante von dem, was in 
einem der früheren PROGMEM Strings steht.

Beispiel:
In ff.c hatte ich ein paar Variablen per Uart anzeigen lassen:
1
xprintf(PSTR("ff_Test_pdrv=%d\n"), fs->pdrv);
2
xprintf(PSTR("ff_Test_stat=%d\n"), stat);
3
xprintf(PSTR("f_mount: vol=%d\n"), vol);
4
xprintf(PSTR("f_open > find_volume res:%d\n"), res);
5
xprintf(PSTR("f_open > end:%d\n"), res);

In der Main habe ich ebenfalls ein paar Ausgaben mit PSTR und auch ein 
paar mit einem normalen String machen lassen:
1
int variable = 44;
2
xprintf("zeile %d \n", 1); //=1
3
xprintf("%s \n","String"); //332
4
xprintf("%d \n", 1); //_mount: vol=1
5
xprintf("%d \n", variable); //_mount: vol=44
6
xprintf("%s %d \n","Hallo",i); //_Test_stat=344

Als Kommentar daneben steht was die Zeile ausgibt.
Wie man sieht, sind es teilweise Fragmente von dem, was zuvor aus dem 
PROGMEM heraus ausgegeben wurde. ("_mount: vol=" und "_Test_stat=")

Wenn ich jetzt sowas machen wie:
1
const char eintest[]={"%s \n"};
2
xprintf(eintest,"String");
Dann kommen für diese und die nachfolgende Zeile nur noch wirre Zeichen 
raus, als ob er irgendwo in den Speicher greift. 
([01]âè[0E]ñ[1C]÷[01]Î[01] usw.)

Mit einem globalen
1
const char eintest[] PROGMEM ={"%s \n"};
und einem
1
xprintf(eintest,"String");
funktioniert es hingegen wie gewünscht, da der String jetzt im Progmem 
steht.


Kann mir jemand erklären, wieso sich die Funktion so verhält? Scheinbar 
kann sie ja mit dem Zeiger in den normalen Ram nicht viel anfangen.
Und wieso kommt bei der Variante mit dem separaten String so etwas 
komisches raus?

Schöne Grüße

von Felix U. (ubfx)


Lesenswert?

Guck dir mal die implementation von xprinft an:

https://github.com/tmk/elm-chan_xitoa/blob/master/xitoa.S#L194

das _LPMI makro benutzt die lpm instruction [1], die liest wie der Name 
schon sagt aus dem Program Memory. Wenn du aus dem SRAM lesen wolltest, 
könntest du z.B. LD [2] benutzen.

[1] http://www.atmel.com/webdoc/avrassembler/avrassembler.wb_lpm.html
[2] http://www.atmel.com/webdoc/avrassembler/avrassembler.wb_LDD_Z.html

von Sebastian V. (sebi_s)


Lesenswert?

Karl schrieb:
> Dann kommen für diese und die nachfolgende Zeile nur noch wirre Zeichen
> raus, als ob er irgendwo in den Speicher greift.

Du hast es ja selbst schon gemerkt. Wenn eine Funktion einen Zeiger ins 
PROGMEM erwartet, du aber einen Zeiger ins RAM übergibst kann alles 
mögliche passieren. Von irgendwelchen Fragmenten aus Strings die 
irgendwo im Flash liegen bis zu kompletten Müll.

von Arno (Gast)


Lesenswert?

Karl schrieb:
> Kann mir jemand erklären, wieso sich die Funktion so verhält? Scheinbar
> kann sie ja mit dem Zeiger in den normalen Ram nicht viel anfangen.
> Und wieso kommt bei der Variante mit dem separaten String so etwas
> komisches raus?

Weil der AVR eine Harvard-Architektur hat, d.h. getrennte Adressbereiche 
für Flash und RAM. Wenn du Daten an Adresse 17 lesen willst, musst du 
dazu sagen, ob der Rechner im RAM oder im Flash lesen soll - indem du 
den richtigen Befehl verwendest (in Assembler LD und co. für RAM und LPM 
und co. für Flash, in der libc für Flash Funktionen mit _P am Ende, bei 
anderen Projekten musst du eben nachsehen...).

C braucht ein paar Krücken, um das Prinzip zu verstehen, und erlaubt 
generell recht freie Typkonvertierungen, daher gibt es keine 
Fehlermeldung, wenn du einen String im RAM anlegst (zum Beispiel an 
Adresse 42) und dessen Adresse an eine Funktion übergibst, die aus dem 
Flash liest. Was sie da liest, ist Zufall - vielleicht steht an der 
Adresse 42 im Flash ein anderer String, vielleicht auch Programmcode.

Wenn ich das richtig im Kopf habe, werden explizit deklarierte Strings 
(const char [] = "abcde") und implizit deklarierte Strings ("abcde") in 
verschiedenen Speichersektionen im RAM zusammengefasst, daher werden bei 
deinem ersten Test die RAM-Zeiger bei deinem Programm zufällig alle an 
eine Stelle im Flash zeigen, in dem die _PSTRs liegen, daher kommen mehr 
oder weniger sinnvolle "Fragmente" zusammen, im zweiten Test zeigen die 
RAM-Zeiger dagegen in den Programmcode, deswegen kommt Zeichensalat. Und 
vermutlich erst sehr spät eine '\0' für String-Ende, weshalb die 
Funktion wahrscheinlich auch noch viel zu viele Zeichen aus dem Flash 
ins RAM kopiert und damit das RAM überläuft.

MfG, Arno

von Scott Meyers (Gast)


Lesenswert?

Die einzig sinnvolle Antwort, die man auf so etwas geben kann, ist:

Stop using C!

von Klaus (Gast)


Lesenswert?

Sebastian V. schrieb:
> Wenn eine Funktion einen Zeiger ins
> PROGMEM erwartet, du aber einen Zeiger ins RAM übergibst kann alles
> mögliche passieren.

Sollte der Compiler da nicht mindestens warnen oder es gar nicht 
zulassen?

MfG Klaus

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Scott Meyers schrieb:
> Die einzig sinnvolle Antwort, die man auf so etwas geben kann,
> ist:
> Stop using C!

Das ist kompletter Unsinn. Dieses konkrete Problem hast Du nur bei µCs 
mit Harvard-Architektur - und nicht nur in C: auch in Assembler musst Du 
verschieden auf die unterschiedlichen Speicherbereiche Flash und RAM 
zugreifen.

Bei Neumann Prozessoren gibt es dieses Problem schlichtweg nicht. Schon 
von daher ist Deine pauschale Aussage obsolet - um es mal höflich 
auszudrücken.

: Bearbeitet durch Moderator
von Scott Meyers (Gast)


Lesenswert?

Deine Antwort ist leider völliger non-sense!

Es geht um Typ-Sicherheit, damit solche Fehler erst gar nicht passieren. 
Das Problem hätte man mit den entsprechenden DT in C++ gar nicht. Das 
Programm würde schlicht nicht kompilieren!

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Scott Meyers schrieb:
> Es geht um Typ-Sicherheit, damit solche Fehler erst gar nicht passieren.

Sag das doch gleich. Du hattest ohne jegliches Zitat Deiner Vorredner 
einfach nur diesen Satz "Stop using C" pauschal in den Raum geworfen. 
Das war so allein für sich hingerotzt leider nicht verständlich.

von Scott Meyers (Gast)


Lesenswert?

Was sollte denn sonst gemeint sein! Denn genau das (keine Typsicherheit) 
ist das Ursprungsproblem!!!

von Arno (Gast)


Lesenswert?

Klaus schrieb:
> Sebastian V. schrieb:
>> Wenn eine Funktion einen Zeiger ins
>> PROGMEM erwartet, du aber einen Zeiger ins RAM übergibst kann alles
>> mögliche passieren.
>
> Sollte der Compiler da nicht mindestens warnen oder es gar nicht
> zulassen?

Es gar nicht zuzulassen ist bei der hardwarenahen Programmierung mMn 
grundsätzlich falsch, denn es wird immer Anwendungsfälle geben, in denen 
man es doch aus irgendeinem Grund braucht. Das ist das schöne an C: Du 
kannst damit unglaublich viel anstellen, weil niemand meint "das kann 
man ja nur benutzen, um sich in den Fuß zu schießen, also verbieten wir 
es".

Dass keine Warnung kommt, wundert mich allerdings auch ein wenig. Teste 
ich heute Abend auf meinem Entwicklungsrechner mal, hier habe ich keinen 
avr-C-Compiler.

Scott Meyers schrieb:
> Was sollte denn sonst gemeint sein! Denn genau das (keine
> Typsicherheit)
> ist das Ursprungsproblem!!!

Schau mal hier im Forum, dann wirst du feststellen, dass die meisten 
Leute mit "Stop Using C" sowas wie "ich bin so geil, ich programmier 
mein Betriebssystem in Assembler" meinen. Dann wird dir sicherlich auch 
Franks Antwort verständlich.

MfG, Arno

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Scott Meyers schrieb:
> Was sollte denn sonst gemeint sein!

Lies Dir diesen Teil des Ursprungspostings nochmal durch:

Karl schrieb:
> Wenn man jetzt versucht, dort einen char* String zu übergeben, der nicht
> explizit im Flash gespeichert wurde, kommt bei mir in der Ausgabe nicht
> das raus, was im String steht, sondern eine Variante von dem, was in
> einem der früheren PROGMEM Strings steht.

Der TO fragte also, wieso er nicht einfach einen "char* String" 
heruntergeben kann. Wenn er nun C++ mit entsprechendem DT verwendet 
hätte, dann hätte er halt einen Error bekommen und hier gefragt, warum 
er diesen Error bekommen hätte. Sein Problem hätte er also mit Deiner 
"Lösung" "Stop using C"  auch nicht gelöst bekommen.

Scott Meyers schrieb:
> Denn genau das (keine Typsicherheit) ist das Ursprungsproblem!!!

Das Ursprungsproblem schon, aber nicht die Ursprungsfrage.

P.S.
Google mal nach "Pratchett exclamation marks". Multiple 
Ausrufungszeichen nerven nicht nur, sondern können ein Bild abgeben, 
welches Du nicht unbedingt beabsichtigst.

: Bearbeitet durch Moderator
von Scott Meyers (Gast)


Lesenswert?

Arno schrieb:
> Schau mal hier im Forum, dann wirst du feststellen, dass die meisten
> Leute mit "Stop Using C" sowas wie "ich bin so geil, ich programmier
> mein Betriebssystem in Assembler" meinen. Dann wird dir sicherlich auch
> Franks Antwort verständlich.

Das mag ja sein, gilt aber nicht für mich.

von Scott Meyers (Gast)


Lesenswert?

Frank M. schrieb:
> Der TO fragte also, wieso er nicht einfach einen "char* String"
> heruntergeben kann. Wenn er nun C++ mit entsprechendem DT verwendet
> hätte, dann hätte er halt einen Error bekommen und hier gefragt, warum
> er diesen Error bekommen hätte.

Falsch: sein eigentliches Problem entsteht ja erst dadurch, dass man ihm 
die Möglichkeit gibt, die Schnittstelle falsch zu benutzen.

> Sein Problem hätte er also mit Deiner
> "Lösung" "Stop using C"  auch nicht gelöst bekommen.

Doch: sinnvoller weise hätte es eine überladene Funktion xprintf() 
gegeben, einmal für die PGM-Strings und einmal für andere (etwa 
std::string, ..., you name it), und das Problem wäre gar nicht erst 
aufgetaucht.

von Arno (Gast)


Lesenswert?

Scott Meyers schrieb:
> Arno schrieb:
>> Schau mal hier im Forum, dann wirst du feststellen, dass die meisten
>> Leute mit "Stop Using C" sowas wie "ich bin so geil, ich programmier
>> mein Betriebssystem in Assembler" meinen. Dann wird dir sicherlich auch
>> Franks Antwort verständlich.
>
> Das mag ja sein, gilt aber nicht für mich.

Und wenn du nur "Stop Using C" schreibst, wie sollte Frank das erkennen?

Scott Meyers schrieb:
>> Sein Problem hätte er also mit Deiner
>> "Lösung" "Stop using C"  auch nicht gelöst bekommen.
>
> Doch: sinnvoller weise hätte es eine überladene Funktion xprintf()
> gegeben, einmal für die PGM-Strings und einmal für andere (etwa
> std::string, ..., you name it), und das Problem wäre gar nicht erst
> aufgetaucht.

Das ist in manchen Kontexten sinnvoll und tatsächlich eine 
Funktionalität von C++ oder Java, die ich in C oft vermisse.

In anderen Kontexten ist es sinnvoller, wenn die PGM-Strings eine 
.toString-Methode haben, die implizit aufgerufen wird, wenn die Funktion 
einen "normalen" String erwartet. In vielen Anwendungen ist der Aufwand 
für beides zu groß (vor allem auf Seiten der Bibliotheksentwickler, die 
sich hier ja sogar das _P-Suffix gespart haben - in Extremfällen auch an 
Rechenzeit und/oder Speicherplatz)

Und in wieder anderen Kontexten möchte der Anwender Pointerarithmetik 
verwenden und muss u.U. Pointer verschiedener Typen sogar miteinander 
verrechnen statt "nur" einander zuzuweisen.

MfG, Arno

von Scott Meyers (Gast)


Lesenswert?

Arno schrieb:
> Scott Meyers schrieb:
>> Arno schrieb:
>>> Schau mal hier im Forum, dann wirst du feststellen, dass die meisten
>>> Leute mit "Stop Using C" sowas wie "ich bin so geil, ich programmier
>>> mein Betriebssystem in Assembler" meinen. Dann wird dir sicherlich auch
>>> Franks Antwort verständlich.
>>
>> Das mag ja sein, gilt aber nicht für mich.
>
> Und wenn du nur "Stop Using C" schreibst, wie sollte Frank das erkennen?

Das gilt auch umgekehrt: warum nimmt er das ohne Grund an?

>
> Scott Meyers schrieb:
>>> Sein Problem hätte er also mit Deiner
>>> "Lösung" "Stop using C"  auch nicht gelöst bekommen.
>>
>> Doch: sinnvoller weise hätte es eine überladene Funktion xprintf()
>> gegeben, einmal für die PGM-Strings und einmal für andere (etwa
>> std::string, ..., you name it), und das Problem wäre gar nicht erst
>> aufgetaucht.
>
> Das ist in manchen Kontexten sinnvoll und tatsächlich eine
> Funktionalität von C++ oder Java, die ich in C oft vermisse.
>
> In anderen Kontexten ist es sinnvoller, wenn die PGM-Strings eine
> .toString-Methode haben, die implizit aufgerufen wird, wenn die Funktion
> einen "normalen" String erwartet.

Nennt man Typumwandlungsoperator in C++.
Möglich, ist aber ggf. die schlechtere der Alternativen (nötige Kopien).

> In vielen Anwendungen ist der Aufwand
> für beides zu groß (vor allem auf Seiten der Bibliotheksentwickler, die
> sich hier ja sogar das _P-Suffix gespart haben - in Extremfällen auch an
> Rechenzeit und/oder Speicherplatz)

Nein, denn die speziellen Instruktion, um die Daten aus dem Flash zu 
holen, brauche ich eh!

> Und in wieder anderen Kontexten möchte der Anwender Pointerarithmetik
> verwenden und muss u.U. Pointer verschiedener Typen sogar miteinander
> verrechnen statt "nur" einander zuzuweisen.

Man sollte aber im eigenen Interesse nur das erlauben, was sinnvoll ist. 
Bzw. den Anwender dazu zwingen, stark über das nachzudenkne, was er da 
an fragwürden Sachen vor hat.

Wie in vielen Kontexten liegt hier der Fehler schon beim Ersteller der 
Bib, nicht beim TO. Der begeht "nur" den Folge-Fehler, dann eben auch C 
zu benutzen ...

von Volle (Gast)


Lesenswert?

Auch bei modernen Harvard Rechner  ist es kein Problem da man i.d.R 
sowohl über den Instruktion als auch über den Datenpfad auf Flash und 
RAM zugreifen kann. Alles hängt am selben Bus oder Crossbar

Sonst wird es auch mühsam mal einen CRC über den eigenen Code zu 
rechnen, oder viele Daten im Flash abzulegen,um das eigene Flash zu 
Programmieren muss man auch mal Code aus dem RAM ausführen können.

Alles nicht so einfach mit den Dogmas.

von Nico W. (nico_w)


Lesenswert?

Der GCC unterstützt schon seit Recht langer Zeit __flash und __memx. 
Dann muss man auch nix überladen.


https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Flash_mit_flash_und_Embedded-C

von Gerhard O. (gerhard_)


Lesenswert?

"Jedem Tierchen sein Pläsierchen!"

Wer das Disziplin und das Wissen hat, kann mit C sehr gut fahren und 
wird diese Freiheit auch lieben. Sicherlich schwirrt dann die Umwelt 
voller Torpedos die den Unaufmerksamen oder den Schlampigen erwischen. 
Aber das ist ja das Schöne daran...

The enemy lurks in the dark; to get you!

Sonst verläßt man sich halt bei größeren Projekten je nach Anforderungen 
auf die voraussehenden Schutzvorkehrungen alternativer Sprachen die den 
Einsatz von traditionellen C möglicherweise sonst zu riskant machen 
würden.

Jeder muß selber wissen was ihm liegt und was notwendig ist.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Klaus schrieb:
> Sebastian V. schrieb:
>> Wenn eine Funktion einen Zeiger ins
>> PROGMEM erwartet, du aber einen Zeiger ins RAM übergibst kann alles
>> mögliche passieren.
>
> Sollte der Compiler da nicht mindestens warnen oder es gar nicht
> zulassen?

Verwende Address Spaces wie __flash zusammen mit 
-Werror=addr-space-convert.

https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html
https://gcc.gnu.org/onlinedocs/gcc/AVR-Options.html#index-Waddr-space-convert

: Bearbeitet durch User
von Scott Meyers (Gast)


Lesenswert?

Johann L. schrieb:
> Klaus schrieb:
>> Sebastian V. schrieb:
>>> Wenn eine Funktion einen Zeiger ins
>>> PROGMEM erwartet, du aber einen Zeiger ins RAM übergibst kann alles
>>> mögliche passieren.
>>
>> Sollte der Compiler da nicht mindestens warnen oder es gar nicht
>> zulassen?
>
> Verwende Address Spaces wie __flash zusammen mit
> -Werror=addr-space-convert.
>
> https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html
> https://gcc.gnu.org/onlinedocs/gcc/AVR-Options.htm...

Was aber nicht bei __attribute__((_progmem_)) bzw. eben prog_char 
funktioniert, d.h. es kommt keine Warnung bzw. Fehler.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Scott Meyers schrieb:
> Was aber nicht bei __attribute__((progmem)) bzw. eben prog_char
> funktioniert, d.h. es kommt keine Warnung bzw. Fehler.

Betrachte PROGMEM als veraltet. Der Ersatz dafür ist __flash.

von Arno (Gast)


Lesenswert?

Bei prog_char kommt auch die entsprechende Warnung:
1
main.c:20:1: warning: 'prog_char' is deprecated: prog_char type is deprecated. [-Wdeprecated-declarations]

PROGMEM ist offiziell noch nicht veraltet, aber ich danke dir für den 
Hinweis auf __flash - werde mal sehen, ob/wie ich meine Projekte darauf 
umstricke.

MfG, Arno

von Scott Meyers (Gast)


Lesenswert?

Frank M. schrieb:
> Scott Meyers schrieb:
>> Was aber nicht bei __attribute__((progmem)) bzw. eben prog_char
>> funktioniert, d.h. es kommt keine Warnung bzw. Fehler.
>
> Betrachte PROGMEM als veraltet. Der Ersatz dafür ist __flash.

Warum soll PROGMEM (__attribute__((progmem))) veraltet sein?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Scott Meyers schrieb:
> Johann L. schrieb:
>> Klaus schrieb:
>>> Sollte der Compiler da nicht mindestens warnen oder es gar nicht
>>> zulassen?
>>
>> Verwende Address Spaces wie __flash zusammen mit
>> -Werror=addr-space-convert.
>>
>> https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html
>> https://gcc.gnu.org/onlinedocs/gcc/AVR-Options.htm...
>
> Was aber nicht bei __attribute__((_progmem_)) bzw. eben prog_char
> funktioniert, d.h. es kommt keine Warnung bzw. Fehler.

Wass auch nicht funktionieren kann weil progmem kein Qualifier ist 
sondern ein Attribut.  Diese regelt nur die ABlage des Codes wie ein 
Section-Attribut, nicht aber den Zugriff.

prog_char* war immer sinnlos, da Attribute nicht Teil des Typs sind, im 
Gegensatz zu Qualifiern.

__flash ist der einzige Weg, per Hochsprache auf den Flash zuzugreifen 
und wird nur als C-Erweiterung unterstützt.  In C++ muss man weiterhin 
auf (Inline-)Assembler zurückgreifen.

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.