Guten Morgen Leute!
Ich habe mal eine Frage bezüglich Pointer in C. Ein uC empfängt über nen
FT232 Kommandos vom PC. Klappt alles bestens. Ich habe eine uart.c und
uart.h, welche im Hintergrund interruptgesteuert Zeichen empfängt und
bei einer fertigen Nachricht ein Flag setzt, sodass das Hauptprogramm
die Nachricht auswerten kann.
In der uart.c habe ich einen Empfangspuffer vom Typ
1
charuart_rx_buffer[UART_RX_BUFFER_SIZE]
Jetzt soll mein Hauptprogramm auf den Inhalt im Puffer zugreifen können.
Also habe ich ebenfalls in der uart.c eine Funktion
1
constchar*uart_return_rx_buffer_address(void)
2
{
3
return(constchar*)uart_rx_buffer;
4
}
Frage: Ist es so generell erstmal der richtige Weg, oder "macht" man das
anders / gibt es einen besseren Weg?
Frage 2: Dass meine Funktion const char * zurückliefert, ist doch wohl
richtig so, da der Empfänger der Pufferadresse den Inhalt ja nicht
verändern, sondern nur lesen soll.
Aber: Wenn ich den cast mit (const char *) nicht mache, dann meckert der
Compiler - kein Fehler, aber eine Warnung. Der uart_rx_buffer ist von
sich aus natürlich nicht const, der wird ja verändert. Aber ohne den
cast passt der uart_rx_buffer nicht zum Typ des Rückgabewertes der
Funktion.
Ist der cast da also unbedingt notwendig, oder sollte man einfach die
Warnung ignorieren?
Gruß und danke!
Hallo!
So wie es aussieht, hast du deine Funktion so deklariert, dass sie
"const char*" zurückgibt. Der Compiler passt deswegen darauf auf, dass
der per return-Anweisung zurückgegebene Wert dem entspricht.
Alternative: Deklariere den Rückgabewert deiner Funktion nicht als
"const char*", sondern als "char*" (erste Zeile in o.g. Code).
Anton schrieb:> Ist der cast da also unbedingt notwendig, oder sollte man einfach die> Warnung ignorieren?
sollte eigentlich ohne Warnung gehen. Welche Warnung bekommst du genau?
Markus Weber schrieb:> Alternative: Deklariere den Rückgabewert deiner Funktion nicht als> "const char*", sondern als "char*" (erste Zeile in o.g. Code).
Hi!
Klar, das würde schon gehen, nur dann fehlt mir ja die Restriktion, den
Puffer von außen nicht verändern zu können.
Hallo,
das kannst Du so machen, mir persönlich würde eine Variante mit FIFO
besser gefallen, weil Dein Ansatz ein Timingproblem aufwirft: Wenn das
erste Byte des nächsten Befehls kommt und Deine Verarbeitung noch nicht
fertig ist, musst Du Dich irgendwie verrenken, um das Byte zu "retten".
Ein FIFO sorgt hier für ein deutlich entspannteres Timing und die Kiste
wird robuster.
Ein char[] ist etwas anderes als ein const char*, deshalb meckert der
Compiler. Allerdings ist es "legal" die Startadresse eines char[] in
einen const char* Zeiger zu casten. Compilerwarnungen zu ignorieren ist
nie eine gute Idee.
Um zu verhindern, dass Dein const char* von oben geschrieben wird,
müsstest Du ihn als "const char const*" deklarieren (ohne Garantie,
meistens halte ich es nicht so strikt :-).
Da Du auf Dein char[] aus dem main()- und dem ISR-Kontext zugreifst,
wäre eine Deklaration als "volatile" zu empfehlen.
Grüße,
Gast
Blablubb schrieb:> das kannst Du so machen, mir persönlich würde eine Variante mit FIFO> besser gefallen, weil Dein Ansatz ein Timingproblem aufwirft: Wenn das> erste Byte des nächsten Befehls kommt und Deine Verarbeitung noch nicht> fertig ist, musst Du Dich irgendwie verrenken, um das Byte zu "retten".> Ein FIFO sorgt hier für ein deutlich entspannteres Timing und die Kiste> wird robuster.
Generell stimme ich zu! Das ist hier aber nicht nötig, da immer nur ein
Befehl abgearbeitet wird und darauf eine Antwort erfolgt. Das ist
gewollt und soll auch so bleiben. Erst wenn die Antwort gesendet wurde,
kann ein neuer Befehl empfangen werden. Wärend der Befehl bearbeitet
wird, ist das weitere Empfangen gesperrt.
Blablubb schrieb:> Allerdings ist es "legal"
Legal ist gut...aber ist es so der richtige Weg?
Blablubb schrieb:> Compilerwarnungen zu ignorieren ist> nie eine gute Idee.
Deswegen frage ich nach :)
Blablubb schrieb:> Um zu verhindern, dass Dein const char* von oben geschrieben wird,> müsstest Du ihn als "const char const*" deklarieren
Noch ein const? Kannst du mir kurz erklären, was welches const dann
bewirkt? Da kann ich dann nämlich nicht mehr folgen. Dachte mit einem
const wäre es erledigt. Leider finde ich solche "komplizierten" Gebilde
auch nicht wirklich in irgendeiner Literatur.
Blablubb schrieb:> Da Du auf Dein char[] aus dem main()- und dem ISR-Kontext zugreifst,> wäre eine Deklaration als "volatile" zu empfehlen.
Sorry, vergessen zu schreiben. Mein rx_buffer ist vom Typ volatile char.
Macht das dann noch einen Unterschied bei der Übergabe der Adresse? Muss
ich dann const volatile char?
...Oh man - das wird langsam etwas kompliziert.
> Mein rx_buffer ist vom Typ volatile char. Macht das dann noch einen> Unterschied bei der Übergabe der Adresse? Muss ich dann const> volatile char?
Gute Frage, das wüsste ich auch gerne.
> ...Oh man - das wird langsam etwas kompliziert.
Deswegen wurden inzwischen andere Programmiersprachen erfunden, bei
denen solche Fragen (zumindest in der Theorie) gar nicht erst aufkommen
sollen.
Wobei ich davon ausgehe, das ich die Ausrottung von C nicht mehr erleben
werde. Darum bleibe ich lieber Freund von C. So hat man weniger Feinde
:-)
OK, jetzt bin ich in der Tat ein wenig verunsichert.
Also nochmal zusammengefasst:
In der uart.c
1
volatilecharrx_buffer[RX_BUFFER_SIZE];
In diesen Puffer kommen per UART die Zeichen. Von woanders möchte ich
auf diesen Puffer zugreifen, also möchte ich gerne die Adresse
übergeben. Was ist jetzt die richtige Funktion dafür?
Zum Verständnis - ist es so erstmal richtig?
1
char*function(void)// gibt einen veränderbaren Zeiger auf einen veränderbaren char zurück
2
3
constchar*function(void)// gibt einen veränderbaren Zeiger auf einen nicht-veränderbaren char zurück
4
5
char*constfunction(void)// gibt einen nicht-veränderbaren Zeiger auf einen veränderbaren char zurück
6
7
constchar*constfunction(void)// gibt einen nicht-veränderbaren Zeiger auf einen nicht-veränderbaren char zurück
- Was ist jetzt mit dem volatile von rx_buffer? Muss das hier auch noch
mit rein?
- In welchem Fall brauche ich einen nicht-veränderbaren Zeiger? Bzw. in
wie fern könnte ich diesen verändern? Hat da mal einer ein Beispiel?
- Welche Variante wäre jetzt für mich die richtige?
Sorry, aber das ist für mich echt kompliziert.
Anton schrieb:> #869-D type qualifier on return type is meaningless>> Kann mich mal bitte jemand aufklären?
Das vor dem Stern (*) gibt an worauf gezeigt wird, und da ist const
volatile nun mal sinnlos.
Nach dem Stern kann man ein const hinzufügen, um dem Compiler zu sagen,
der Pointer soll nicht verändert werden.
Also entweder:
foo schrieb:> Das vor dem Stern (*) gibt an worauf gezeigt wird
OK, gezeigt wird auf ein volatile char
foo schrieb:> Nach dem Stern kann man ein const hinzufügen, um dem Compiler zu sagen,> der Pointer soll nicht verändert werden.
Damit kann dann der Inhalt des Zeigers, also die Adresse des rx_buffers
nicht geändert werden.
Aber der Inhalt des rx_buffers ist doch mit
1
volatilechar*const
nach wie vor veränderbar, oder? Bitte versteht mich nicht falsch, man
könnte jetzt auch sagen, dass ich ja wohl wissen muss, wo mein Programm
wann was ändert, aber ich möchte diese Geschichte einfach mal verstehen.
das volatile ist eigentlich überhaupt nicht notwendig. In der ISR wo die
Daten reingeschrieben werden kann er eh nichts im Register vorhalten.
und bei zugriff über eine Zeiger im normalen Programm erfolgt auch nicht
über Register. (außer man nimmer immer das x. element vom Array, was
aber in der Praxis kaum vorkommt.)
Ich würde hier da volatile komplett weglasse, es stört mehr als es
bringt.
Anton schrieb:> Aber der Inhalt des rx_buffers ist doch mitvolatile char * const> nach wie vor veränderbar, oder? Bitte versteht mich nicht falsch, man> könnte jetzt auch sagen, dass ich ja wohl wissen muss, wo mein Programm> wann was ändert, aber ich möchte diese Geschichte einfach mal verstehen.
Dann musst halt casten, so wie in deinem Code.
1
return(constchar*)uart_rx_buffer;
Der Compiler warnt dich nur, dass du das volatile verlierst.
Volatile heißt ja, da Zugriffe, also auch Lesen Seiteneffekte haben
können. Bei deinem Buffer egal, bei Registern manchmal nicht.
foo schrieb:> dass du das volatile verlierst
Macht das denn nichts aus? Es heißt doch, dass alle Variablen, welche
durch einen Interrupt verändert werden können, als volatile zu
deklarieren sind.
Das ist hier der Fall, da die UART-ISR den Puffer beschreibt. Wenn ich
jetzt die Adresse des Puffers an eine andere Funktion weitergebe,
brauche ich das volatile dann nicht mehr mit zu schleppen? Ich verstehe
den Zusammenhang dann nicht.
Volatile garantiert mir doch, dass stets der Inhalt neu aus dem Speicher
geholt wird und ich somit immer den aktuellen Wert geliefert bekomme.
Wenn ich nun die Adresse weitergebe will ich ja auch den aktuellen Wert.
Oder ist dies hier nicht nötig, da ich mit der Adresse ja sowieso genau
in den Speicher zeige, wo sich der gesuchte Inhalt tatsächlich befindet?
Aber warum muss dann ein Array ebenfalls als volatile deklariert werden?
Muss nicht eh in die Speicherstelle geschrieben werden? Oder ist das
ganze Array noch irgendwo quasi "kopiert" und wird sonst nur "dort"
verändert?
Anton schrieb:> foo schrieb:>> dass du das volatile verlierst>> Macht das denn nichts aus?
Bei dir macht das nichts aus.
Das ist aber in deinem speziellen Programmaufbau so. Im Allgemeinen kann
aber der Compiler nicht wissen, ob das harmlos ist oder nicht, daher
warnt er dich, dass du damit (indirekt) das volatile umgehst bzw.
verlierst.
Die Warnung vom Anfang bezog sich nicht auf das const, sondern darauf,
dass du damit das volatile verlierst.
> Es heißt doch, dass alle Variablen, welche> durch einen Interrupt verändert werden können, als volatile zu> deklarieren sind.
Das ist die Kurzfassung.
Die etwas längere Fassung hat nichts mit Interrupts zu tun. DIe längere
Fassunf besagt, das mit volatile dem Compiler verboten wird,
irgendwelche Annahmen über den Inhalt der Variblen zu treffen, weil sie
sich auf Wegen ver#ndern kann, die für den COmpiler nicht einsichtig
sind.
Hast du also
1
charc;
2
char*pPtr=uart_return_rx_buffer_address();
3
4
c=*pPtr;
5
c=*pPtr;// das sind absichtlich 2 identische Zugriffe!
dann kann in diesem Fall der COmpiler den 2.ten Zugriff wegoptimieren,
weil sich weder pPtr, noch *pPtr in diesem Codestück ändern können. c
kriegt also in beiden Fällen aus Sicht des Compilers jeweils denselben
Wert. Daher kann er den 2.ten Zugriff weglassen.
Wohingegen man dem Compiler hier
c=*pPtr;// das sind absichtlich 2 identische Zugriffe!
dem Compiler explizit mitteilt: das, worauf pPtr zeigt, das kann sich
ändern, ohne das du das mitkriegst. Du musst also beide Zugriffe *pPtr
tatsächlich durchführen!
> Das ist hier der Fall, da die UART-ISR den Puffer beschreibt.
Schau.
Der COmpiler analysiert nicht den Sinn deiner Programmzeilen. Der
Compiler geht nach formalen Regeln vor. Und die besagen nun mal, das der
Verlust eines volatile potentiell ein Problem sein kann. Und das teilt
dir der Compiler mit der Warnung mit. Ob das dann in deinem konkreten
Fall tatsächlich problematisch ist oder nicht, das musst du entscheiden.
Du bist ja immerhin der Programmierer. Wer, wenn nicht er sollte wissen,
ob das 'Problem' harmlos ist oder nicht?
> Volatile garantiert mir doch, dass stets der Inhalt neu aus dem Speicher> geholt wird und ich somit immer den aktuellen Wert geliefert bekomme.> Wenn ich nun die Adresse weitergebe will ich ja auch den aktuellen Wert.
Aber der Aufrufer kann diese Adresse mehrmals benutzen um mehrmals auf
dieselbe Speicherzelle zuzugreifen!
Anton schrieb:> Frage: Ist es so generell erstmal der richtige Weg, oder "macht" man das> anders / gibt es einen besseren Weg?
Generell würde ich keine überflüssigen Parameter übergeben, sondern
direkt auf den (einen) Empfangspuffer zugreifen.
Die Übergabe der Pufferadresse ist nur dann nötig, wenn man mehrere
Puffer zur Auswahl hat.
Und falls die Funktion universell sein soll, muß man auch die
Puffergröße als Parameter übergeben.
Hallo Karl-Heinz!
Vielen Dank für deine ausführliche Antwort.
In meinem Fall - wie ist es denn jetzt richtig? den RX-Puffer deklariere
ich als volatile char.
und wenn ich jetzt die adresse dieses Puffers weitergeben möchte, dann
erfolgt das mit dem volatile
1
volatilechar*uart_return_rx_buffer_address(void)
2
{
3
returnuart_rx_buffer;
4
}
oder ohne:
1
char*uart_return_rx_buffer_address(void)
2
{
3
return(char*)uart_rx_buffer;
4
}
Durch den Cast verschwindet dabei die Warnung vom Compiler.
Wenn ich jetzt zusätzlich den Schreibschutz auf den Puffer haben will,
dann so:
Oder mache ich den Zeiger ebenfalls const, wo ich mich aber frage, wofür
ich das hier brauchen sollte.
Wenn ich jetzt irgendwo einen Zeiger in der main() ins Leben rufe,
welcher die Adresse speichern soll, die mir von der Funktion geliefert
wird, muss ich diesen dann immer identisch zum Rückgabewert der Funktion
wählen? Also für das letzte Beispiel dann
Peter Dannegger schrieb:> Generell würde ich keine überflüssigen Parameter übergeben, sondern> direkt auf den (einen) Empfangspuffer zugreifen.
Ich habe ja nur den einen. Aber der ist in der uart.c und daher kann ich
so nicht darauf zugreifen, es sei denn, ich deklariere den als extern.
Daher dachte ich, ich lasse mir die Adresse von dem geben.
Anton schrieb:> Hallo Karl-Heinz!>> Vielen Dank für deine ausführliche Antwort.>> In meinem Fall - wie ist es denn jetzt richtig?
Indem du darüber nachdenkst, wie der Aufrufer deiner Funktion den
Pointer verwenden wird!
> den RX-Puffer deklariere> ich als volatile char.
Das kann man schon mal in Frage stellen, ob das in deinem Fall überhaupt
notwendig ist.
Sorry. Aber man kann in der Programmierung nur ganz selten nach dem
Muster "Ich mach das einfach so und so, nach Kochrezept" vorgehen. Man
muss jeden Fall einzeln durchdenken. In deinem konkreten Fall musst du
sowohl dein UART Modul, als auch wie es verwendet wird ins Kalkül
ziehen.
Genauso beim volatile. volatile hat den Zweck ein ganz bestimmtes
Problem zu verhindern. Es liegt an dir zu entscheiden, ob dieses Problem
bei dir überhaupt auftreten kann oder nicht. Einfach nur zu sagen "Alle
Variablen, die in einer ISR vorkommen mach ich volatile", das greift
deutlich zu kurz.
> Durch den Cast verschwindet dabei die Warnung vom Compiler.
Logisch.
So ein Cast ist ja letzten Endes nichts anderes als ein
"Ja, ich weiß das die Datentypen rein formal nicht passen. Aber, hör
mal, ich bin der Programmierer und ich übernehme die Verantwortung, dass
das in diesem Fall ok ist. Also: Halts Maul!"
Ich muss zugeben, dass ich mich mit den Zeigern und jetzt scheinbar auch
mit dem volatile etwas schwer tue - das sieht man sicher auch. Dass der
RX-Puffer von mir ein volatile bekommen hat, geht unter anderem auch
daher hervor, dass es hier in der UART-Erklärung auf mikrocontroller.net
so gemacht wird.
Ich gebe zu, dass ich nach deiner Erklärung garnicht mehr so sicher bin,
ob volatile ja oder nein. Brauche ich es hier oder nicht. Ich weiß es
nicht!
Ebenso mit der Übergabe der Adresse und der weiteren Verarbeitung durch
einen Zeiger, welcher den Rückgabewert aufnimmt. Ich weiß, dass es vllt.
etwas viel verlangt ist, aber kann es mir evtl. mal jemand anhand meines
Beispieles hier erklären wie es richtig ist UND vor allem WARUM?
Mein Code funktioniert im Prinzip schon problemlos, aber ich will ja
nicht dumm sterben und es gerne richtig machen. Unnötig volatiles
verteilen ist ja auch nicht Sinn der Sache und anderes umcasten, obwohl
ggf. garnicht nötig, das will ich auch nicht.
Könnte mir jemand den gefallen tun - ich denke mit einer Erklärung
anhand des vorliegenden Beispiels kann ich es dann hoffentlich auch auf
andere Fragestellungen anwenden. Sonst stehe ich demnächst wieder hier.
Gruß, Anton
Volatile ist in der Tat ein schwieriges Problem. Und wenn man das
volatile noch als Parameter übergeben will, wird das immer komplexer.
Das volatile Problem entsteht dadurch, daß der Compiler Schleifen
erkennt und dann möglichst viele Zugriffe vor die Schleife verlagern
will.
Dazu kommt der weitere Aspekt, daß er Funktionen inlinen möchte und
damit deren Zugriffe plötzlich zur Schleife gehören und mit optimiert
werden können.
Interrupts werden aus Sicht des Compilers niemals aufgerufen und damit
ist er der Meinung, die Variable ändert sich nicht innerhalb der
Schleife, kann also davor einmalig in ein Register gelesen werden.
Eine Lösung ist, nur den kritischen Zugriff als volatile zu casten:
1
// force access of interrupt variables
2
#define IVAR(x) (*(volatile typeof(x)*)&(x))
3
4
staticuint8_trx_in;
5
staticuint8_trx_out;
6
7
uint8_tukbhit0()
8
{
9
returnrx_out^IVAR(rx_in);// rx_in modified by interrupt !
Anton schrieb:> Oh man...solche Ausdrücke...#define IVAR(x) (*(volatile> typeof(x)*)&(x))> ...geben mir zu verstehen, dass ich noch viel lernen muss :-\
Warum? Einfach auflösen.
1
#define IVAR(x) (*(volatile typeof(x)*)&(x))
aus:
1
IVAR(rx_in)
wird
1
(*(volatileunsignedchar*)&(rx_in))
Jetzt klar?
Wobei man sagen muss, dass typeof nicht ANSI ist, sondern eine GNU
extension.
Man könnte auch ANSI konform schreiben.
1
#define IVAR(x,y) (*(volatile y*)&(x))
2
3
staticuint8_trx_in;
4
staticuint8_trx_out;
5
6
uint8_tukbhit0()
7
{
8
returnrx_out^IVAR(rx_in,uint8_t);// rx_in modified by interrupt !
>> bewirkt vom Compiler:>> #869-D type qualifier on return type is meaningless
Das bezieht sich wohl auf das const hinter dem *.
Das ist sinnlos, weil es ein Rückgabe*wert* und keine Variable ist.
Jürgen
Blablubb schrieb:> müsstest Du ihn als "const char const*" deklarieren (ohne Garantie,
Das ist Schwachsinn, denn beide "const" beziehen sich auf das "char".
const und volatile beziehen sich immer auf das Element unmittelbar
davor, außer wenn sie am Anfang stehen. Schlaue Leute schreiben
deswegen der Einheitlichkeit halber "char const *" statt "const char *".
Jürgen
Ehe ich anfange, einzelne Zugriffe volatile zu casten, nehme ich doch
lieber gleich eine Memory-Barrier (wie heißt die eigentlich auf
deutsch?).
Jürgen
Ich habe das hier mal mit verfolgt und hatte gehofft, dass die Lösung
noch von irgendwem kommt...ich habe nämlich ähnliches.
Zumal das ja auch ne grundsätzliche Sache ist. Kann nicht einer von den
versierteren Programmieren das Rätsel auflösen?
Da wäre auch ich dankbar.
Ferdinand
Leider fehlt bei Antons Fragen das Wesentliche, nämlich woher der Leser
des Puffers weiß, ob bzw., wo und wieviele Daten in dem Puffer sind.
Das einfachste Beispiel wäre ein Byte
1
char Daten;
und ein Flag, ob die Daten gültig sind
1
boolean Daten_gueltig;
Schreiben geht dann so:
1
Daten = ...
2
Daten_gueltig = true;
und lesen
1
if (Daten_gueltig)
2
{
3
... = Daten;
4
}
Ohne Optimierung funktioniert das auch so. Mit Optimierung kommt der
Kompiler womöglich auf die Idee, die Daten in einer anderen Reihenfolge
zu lesen. Z.B. Daten_gueltig wird gesetzt, bevor Daten den korrekten
Wert hat, also werden dann auch evtl. falsch Daten gelesen. Oder Werte,
die einmal in ein Register gelesen wurden, werden nicht nochmal aus dem
Speicher gelesen (das Warten auf Daten_gueltig in einer While-Schleife
kann dann etwas länger dauern).
Deklarierst du die Daten jetzt volatile, also
1
char volatile Daten;
2
boolean volatile Daten_gueltig;
dann dürfen die Speicherzugriffe nicht mehr umsortiert oder weggelassen
werden, es wird also so funktionieren. Aber es werden auch alle
Möglichkeiten zur Optimierung bzgl. dieser Variablen verbaut, selbst da,
wo die genaue Beibehaltung der Zugriffe nicht nötig wäre.
Eine Alternative zu volatile ist es, an den Stellen, wo es auf die
Reihenfolge ankommt, eine Memory-Barrier zu verwenden:
1
Daten = ...
2
memory_barrier();
3
Daten_gueltig = true;
und lesen
1
if (Daten_gueltig)
2
{
3
memory_barrier();
4
... = Daten;
5
}
Über die Memory-Barrier hinweg dürfen keine Speicherzugriffe verschoben
werden, das Flag wird also auf jeden Fall nach den Daten geschrieben und
vor den Daten gelesen. Wie die Memory-Barrier genau realisiert wird,
hängt vom Prozessor und vom Kompiler ab.
Bei GCC sollte es das folgende tun (Makro definieren!):
Ferdinand schrieb:> ich habe nämlich ähnliches.
Wenn es nur ähnlich ist, schildere es einfach in einem neuen Thread.
Auch in der Programmierung gibt es keinen Deckel, der auf alle Töpfe
paßt.