haschenk

RCLine User

  • »haschenk« ist der Autor dieses Themas

Beruf: Dipl. Ing.

  • Nachricht senden

1

Montag, 27. Mai 2019, 17:52

Was ist das für eine Variablen-Deklaration ?

Hallo,

falls hier ein erfahrener C++ Programmierer mitliest, wäre ich für eine Hilfe dankbar.

Ich knoble daran, was folgende Deklaration bewirkt:

byte addresses [] [5] = {"ADDR1", "ADDR2"};

Daß es um ein (oder 2 ?) Array(s) von bytes geht, ist mir noch klar. Die Zeichen von ADDRx sind offenbar ASCII, für jedes Zeichen 1 Byte.
Aber was bewirkt "[]" ? Scheint ein Operator zu sein... In Zusammenhang mit den geschweiften Klammern ?
Ich habe natürlich schon danach gegoogelt, finde aber keine (für mich) verständliche Erklärung.
Könnte man das nicht auch einfacher (und für einen "Nicht-Spezialisten" verständlicher) deklarieren ?

Ergänzend:
Im weiteren Verlauf des Programms kommt dann nur addresses[0] und addresses[1] vor.
Es geht um die Kommunikation zwischen 2 Funkmodulen nrf24L01 und Arduinos, die in einer Telemetrie-Funkstrecke zwischen Modell und Pilot eingesetzt werden sollen.

Wer kann mir das erklären ?

Danke und
Gruß,
Helmut

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »haschenk« (27. Mai 2019, 18:03)


Klaus(i)

RCLine User

Wohnort: Achim b. Bremen

Beruf: Mensch

  • Nachricht senden

2

Montag, 27. Mai 2019, 18:38

Ich bin auch kein Profi, aber es scheint sich um die Deklarations eines Arrays bestehend aus 5-Bytes-Arrays zu handeln.
Bei den geschweiften Klammern handelt es sich um die Initialisierung des Arrays mit zwei 5-Byte-Arrays (in diesem Fall Zeichenfolgen a 5 Byte).
Hoffentlich habe ich mich verständlich ausgedrückt.

Gruß, Klaus

3

Montag, 27. Mai 2019, 18:48

Es geht darum, dass man Arrays nicht direkt kopieren kann ... darum wird beim initialisieren die [5] hinten drann gehängt, damit der parser weiss, wie er mit den pointern umgehen soll. Wird quasi reserviert., da "ADDR1" ja nicht direkt im adresses[0] angelegt wird sondern nur die entspr. pointer pro byte.

Gäbe es einen Datentyp "5BytesType" könnte man auch 5BytesType addresses [] = {"ADDR1", "ADDR2"}; schreiben ... den gibt es aber nicht :D
Gruß Stefan

Ich hab auch ein Flugzeug :dumm:

Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von »van3st« (27. Mai 2019, 19:18)


4

Montag, 27. Mai 2019, 23:52

das ist ein mehrdimensionales array

https://de.wikibooks.org/wiki/C-Programmierung:_Arrays

Abschnitt "Fehlende Größenangabe bei vollständig initialisierten mehrdimensionalen Arrays"

Im Grunde "schlampig" programmiert - oder weil es Beispielcode ist, auf das abstrakte Minimum reduziert. Sind ja mehr als diese beiden Adressen möglich, aber sie müssen ja irgendwie an die Boards übergeben werden und "Sender" und "Empfänger" die zusammengehörigen Adressen kennen.

5

Mittwoch, 29. Mai 2019, 15:02

Es geht darum, dass man Arrays nicht direkt kopieren kann ... darum wird beim initialisieren die [5] hinten drann gehängt, damit der parser weiss, wie er mit den pointern umgehen soll. Wird quasi reserviert., da "ADDR1" ja nicht direkt im adresses[0] angelegt wird sondern nur die entspr. pointer pro byte.

Hier wird aber kein Array of char Pointer angelegt sondern ein mehrdimensionales char Feld. Also direkt die Werte, keine Zeiger. Die 5 braucht er, weil er ja wissen muß, wo er mit dem nächsten Wert anfängt. Nur die Anzahl der Elemente der oberen Dimension ist offen.

Der Fehler ist aber, daß ein Feld mit 5 Zeichen mit 6 Zeichen initialisiert werden soll. Der String "ADDR1" hat 5 Zeichen, aber immer auch eine Null Terminierung. Der Compiler sollte das so gar nicht annehmen.

Was ginge wäre: char addresses[][5] = { "ADR1", "ADR2", {'A' , 'D' ,'D' ,'R' ,'3'} }

RK

6

Mittwoch, 29. Mai 2019, 22:37

Zitat

Der Fehler ist aber, daß ein Feld mit 5 Zeichen mit 6 Zeichen initialisiert werden soll. Der String "ADDR1" hat 5 Zeichen, aber immer auch eine Null Terminierung. Der Compiler sollte das so gar nicht annehmen.

Ob der Compiler da tatsächlich noch ne 0 dranhängt wäre Ich mir jetzt nicht sicher.
Es wird ja keine Stringverarbeitung eingesetzt.
Eventuell schreibt er auch nur die entsprechenden Zahlenwerte der ASCII Zeichen in den Speicher.
Fügt der Compiler aber die 0 tatsächlich ein belegt er den folgenden Speicherplatz mit fatalen Folgen. - Z.B. wenn dabei eine Variable überschrieben wird.
Das könnte man aber mit dem Simulator austesten.

Eigentlich macht die Funktion so keinen Sinn, da hier ein mehrdimensionales Array mit 2 x 5 Einträgen generiert wird.
Der einzige Vorteil ist, das man eine ADDR3 einfügen könnte ohne die 2 in eine 3 umändern zu müssen.

Ich geb da lieber die konkrete Größe ein, da behalt Ich dann besser den Überblick.

7

Mittwoch, 29. Mai 2019, 23:09

Was fuer ein Compiler ist das und was ist das Ziel?
byte addresses [] [5] = {"ADDR1", "ADDR2"};
ergibt fuer mich kein erkennbares Ziel.
2 Dimensionales Arrray ok. Aber nur das erste wird gefuellt.
Gruss
Thomas
🖖

Vielleicht ist Wissen doch nicht die Antwort auf alles

8

Donnerstag, 30. Mai 2019, 00:31

Zitat

Der Fehler ist aber, daß ein Feld mit 5 Zeichen mit 6 Zeichen initialisiert werden soll. Der String "ADDR1" hat 5 Zeichen, aber immer auch eine Null Terminierung. Der Compiler sollte das so gar nicht annehmen.

Ob der Compiler da tatsächlich noch ne 0 dranhängt wäre Ich mir jetzt nicht sicher.
Es wird ja keine Stringverarbeitung eingesetzt.
Eventuell schreibt er auch nur die entsprechenden Zahlenwerte der ASCII Zeichen in den Speicher.
Fügt der Compiler aber die 0 tatsächlich ein belegt er den folgenden Speicherplatz mit fatalen Folgen. - Z.B. wenn dabei eine Variable überschrieben wird.
Das könnte man aber mit dem Simulator austesten.

Ich habs mit Visual Studio ausprobiert und mir das Array (mit korrigierten Grenzen) angeschaut. Dort wird es direkt gar nicht erst so compiliert. Und ja, eine 0 wird angehängt. Zeichen in "" sind nun mal (char)Strings.

Zitat

Eigentlich macht die Funktion so keinen Sinn, da hier ein mehrdimensionales Array mit 2 x 5 Einträgen generiert wird.
Der einzige Vorteil ist, das man eine ADDR3 einfügen könnte ohne die 2 in eine 3 umändern zu müssen.

Ich geb da lieber die konkrete Größe ein, da behalt Ich dann besser den Überblick.

Das macht aber Erweiterungen schwieriger. Dann muß man immer mitzählen, was dazukommt. Mit offenen Grenzen kann man das den Compiler erledigen lassen. Bei 3-4 Elementen ist das egal, aber bei z.B. 50-100 sieht es anders aus.

Was fuer ein Compiler ist das und was ist das Ziel?
byte addresses [] [5] = {"ADDR1", "ADDR2"};
ergibt fuer mich kein erkennbares Ziel.
2 Dimensionales Arrray ok. Aber nur das erste wird gefuellt.

Was meinst du mit "das erste" ? Hier werden einfach die Zeichen hintereinander in das Array geschrieben. Mit [][6] ergibt das ein [2][6] mit dem Inhalt im linearen Speicher:
A D D R 1 /0 A D D R 2 /0

RK

9

Donnerstag, 30. Mai 2019, 00:40

[2][5] Wobei die 5 fuer mich keinen Sinn ergibt. Auch nicht das erste Array.
Gruss
Thomas
🖖

Vielleicht ist Wissen doch nicht die Antwort auf alles

10

Donnerstag, 30. Mai 2019, 00:45

[2][5] Wobei die 5 fuer mich keinen Sinn ergibt. Auch nicht das erste Array.

Nochmal die Frage, was meinst du mit erstes Array ? Es gibt nur eines.

Ich habs auf [2][6] erweitert, weil es nur so mit der vorgegebenen Initialisierung funktioniert. Die 5 oder 6 ist die niederwertige Dimension, die [] oder[2] die höhere. Also 2 Elemente a 5 oder 6 byte hintereinander.

RK

11

Donnerstag, 30. Mai 2019, 00:56

Also passt [][5]. Auch wenn ich es so nich anwenden wuerde.
Gruss
Thomas
🖖

Vielleicht ist Wissen doch nicht die Antwort auf alles

12

Donnerstag, 30. Mai 2019, 13:51

[][5] kann passen oder auch nicht: { "ADDR1","ADDR2"} macht ja 2 Strings zu 6 Chars, wobei der 6. einen 0 ist. Theoretisch sollte der Compiler maulen, wenn man auf [..][5] zugreift ... hätte man [][6], dann gäb's keine warnung, man könnte [...][5] überschreiben und sich an spannenden Wffekten freuen ;)

13

Donnerstag, 30. Mai 2019, 14:16

[][5] kann passen oder auch nicht: { "ADDR1","ADDR2"} macht ja 2 Strings zu 6 Chars, wobei der 6. einen 0 ist. Theoretisch sollte der Compiler maulen, wenn man auf [..][5] zugreift ...

Mein Compiler mault schon beim Initialisieren. D.h. dabei kann nichts schief gehen, weil er dabei ja auch alle Infos hat. Den fehlerhaften Zugriff ignoriert er aber (VC6). In C ist ja viel Pointerarithmetik möglich, da ist sowas zwar nicht schön, aber möglich. Er überschreibt einfach das erste Zeichen des nächsten Elements, das 'A'.

Ich habs jetzt nochmal mit dem Arduino AVR Compiler getestet, da kommt keine Fehlermeldung, auch wenn der String deutlich länger ist. Allerdings habe ich nicht probiert, was er dann zur Runtime macht.
Der Arduino ESP32 Compiler findet den Fehler schon. Wobei beim AVR aufgrund der Architektur (kein Code im RAM) die Fehlermöglichkeiten wesentlich geringer sind.

Zitat

hätte man [][6], dann gäb's keine warnung, man könnte [...][5] überschreiben und sich an spannenden Wffekten freuen ;)

Kommt auf die Nutzung an. Wenn man die Werte als char String nutzen will, dann ja. Nur dann würde man sowieso eher ein char *addresses[] = ... nehmen. Aber die übersehene String Nullterminierung ist immer gut für interessante Fehler.

RK

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »rkopka« (30. Mai 2019, 14:30)


14

Donnerstag, 30. Mai 2019, 18:12

Lustig. Schau mal, bei [][5] fehlt das \0, bei [0][6] ist's drinnen. Das war jetzt ein bischen überraschend für mich:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main() {
    char a[][5]={"ADDR1","ADDR2","\0"};
    puts(a[0]);
    puts(a[1]);
    a[0][5]='-';
    puts(a[0]);

    char b[][6]={"ADDR1","ADDR2","\0"};
    puts(b[0]);
    puts(b[1]);
    b[0][5]='-';
    puts(b[0]);
}


Ausgabe:

Quellcode

1
2
3
4
5
6
ADDR1ADDR2
ADDR2
ADDR1-DDR2
ADDR1
ADDR2
ADDR1-ADDR2

15

Donnerstag, 30. Mai 2019, 18:23

Lustig. Schau mal, bei [][5] fehlt das \0, bei [0][6] ist's drinnen. Das war jetzt ein bischen überraschend für mich:

Fehlende Werte werden automatisch mit 0 gefüllt. Also sollten die 0en auch noch bei [7] alle drin sein. Eher finde ich interessant, daß einige Compiler das Füllen mit zu großen Elementen erlauben, andere nicht. Was hast du für einen Compiler benutzt ? Ich bin da nicht der C-Guru, aber ich werde Montag mal einen solchen dazu befragen.

RK

16

Donnerstag, 30. Mai 2019, 18:26

Nein, das mein' ich nicht. ".." ist ein String und endet mit \0. Warum schreit der Compiler nicht bei [][5], dass die Initalisierrung mit zu vielen Werten erfolgt? Bei [][4] z.b. schreit er.

haschenk

RCLine User

  • »haschenk« ist der Autor dieses Themas

Beruf: Dipl. Ing.

  • Nachricht senden

17

Donnerstag, 30. Mai 2019, 23:05

Hallo,

ich denke, es ist an der Zeit, daß ich noch etwas mehr zum Thema sage.
Erstmal danke für die Anworten; vor allem die ersten 3 davon haben meinem Verständnis auf die Sprünge geholfen. Daß da noch eine weitere Diskussion entstanden ist, hätte ich nicht erwartet; aber aus dieser schließe ich, daß meine Frage doch nicht ganz trivial war.

Ich sehe die Deklaration jetzt so:
Die leeren eckigen Klammern am Anfang sagen dem Compiler, daß er so viele "5Byte-Arrays" einrichten muß, wie rechts in den geschweiften Klammern als Addressen aufgeführt sind.
Dazu aber weiter unten noch was...

Das zweite Wichtige zu sagen ist, daß die ganze ganze "Kommunikation" vom ersten Versuch an einwandfrei funkltioniert hat (da hab' ich wohl Glück gehabt mit den download-Versionen und der Hardware...). Es geht also "nur" um's Verständnis und evtl. Änderungen gem. meinen Vorstellungen.

Die Hardware besteht aus je einem Arduino (Typ fast egal) und einem Funkmodul nrf24L01+ (das "+" kennzeichnet die neueste Version, die aber auch schon ein paar Jahre alt ist) auf Sender und Empfängerseite. Damit kann man "komfortabel" eine bidirektione Verbindung herstellen.

Für diejenigen, die Arduino nicht kennen:
Die Arduinos werden in einer eigenen Sprache programmiert, die aber nichts anderes als eine vor dem User "versteckte" C/C++ Programmierung ist. Hinter den einfachen Arduino-Befehlen stecken immer C oder C++ Funktionen/Methoden. Man kann Arduinos auch (effizient) ganz nur in C++ programmieren. Der Compiler ist der bekannte "GCC".
Die schwierigeren Aufgaben werden mit Hilfe von "Libraries" gelöst, von denen ein Teil schon im Arduino-Paket enthalten ist (z.B. Kommunikation über I2C- oder SPI-Bus); wenn man es braucht, werden die Libraries dem Compiler mittels "includes" verfügbar gemacht. Im gegebenen Beispielsfall gehören "SPI.h" und "LiquidCrystal.h" schon zum Standard-Umfang von Arduino, "RF24" ist die Library für das Funkmodul. Solche "Fremd-Libraries" für Bausteine aller Art gibt's zu Hunderten im Web, denn die Arduino-Community ist (weltweit) riesig. Eine Library enthält immer mindestens 2 Files, xx.h und xx.cpp, dazu können noch Beispiele, Schlagwörter, readme etc. kommen.

In der Addressen-Deklaration vom "NRF" können max. 7 Addressen vorkommen; seine eigene plus max. 6 andere (ein "master"-NRF kann mit max. 6 "slaves" kommunizieren). Lt. Datenblatt kann man die Addressen mit 3, 4 oder 5 Bytes adressieren; warum das nicht mit 1 Byte geht, weiß ich nicht.
Aufgefallen ist mir aber, daß der vom Compiler angezeigte Programmspeicherbedarf mit jeder zusätzlichen Addresse um 4 Bytes steigt (mit Ausnahme von 3 auf 4, da sind es 6). Ob da eine Terminierung mit "NUl" enthalten ist ?

Die eigentliche Packetgröße ("Payload") beträgt beim NRF24 maximal 32 Bytes; da könnte ich binär 10 Sensordaten zu 2 Bytes + Delimiter unterbringen. Es sind aber "ASCII-Bytes"....

Meine Test-Payload hat 13 Bytes. Wenn ich in der zugehörigen Array-Deklaration statt 34 nur 15 oder 14 Bytes einsetze, geht's noch gut; bei 13 ist dann das letzte angezeigte Zeichen schon falsch; also ist die payload offenbar ein NUL-terminierter String. Das aber nur am Rande.

Die NRFs sind interessante Teile (eigentlich für 's IoT gedacht), recht preisgünstig (ca. 3 bis 3,50 Euro) und leicht zu programmieren. Aber die HF-Ausgangsleistung (programmierbar) beträgt max. nur 1 mW (0 dBm) und die minimale 16 µW (-18 dBm). Mit der minimalen Leistung und der "Print-Antenne" komme ich problemlos noch quer durch die ganze Wohnung (knappe 20 m, 2 Wände). Ich bin gespannt, wie weit es im Freien mit max. Leistung und "richtiger" Lambda/4-Antenne geht- 100 m würden mir reichen. Wenn das nicht geht (der "Härtefall" ist der Betrieb eines Empfängers in Nähe des RC-Senders), dann gibt's natürlich auch noch leistungsfähigere Funkmodule (auch in einem anderen ISM-Band), aber die kosten dann halt das 5 - 10 fache.

Ich hänge unten mal das Empfangsprogramm an; ist ja nicht groß (das Senderprogramm später, falls es jemand interessiert). Gegenüber dem Original von Github sind nur 2 unwesentliche Änderungen drin: Zum Einen 2 I/Os am Arduino verschoben (damit's auf einen Stecker+Bandleitung passt), zum Anderen ein LCD eingefügt (damit ich die serielle Verbindung zum PC nicht brauche).

In dem Prog ist auch noch 1 Befehl drin, der (zumindest für mich als C++ Anfänger) bemerkenswert ist: "radio.read(&got_Payload, sizeof(got_Payload) )" .
Mit dem &-Operator wird da auf got_Payload zugegriffen; das ist doch imho eine Zeiger-Operation, obwohl nirgendwo zuerst ein Zeiger deklariert wurde. Das gibt's wohl nur in C++ ....

Jetzt mache ich aber Schluß, der Post ist schon viel zu lang,

Gruß,
Helmut
»haschenk« hat folgende Datei angehängt:

18

Donnerstag, 30. Mai 2019, 23:57

Das zweite Wichtige zu sagen ist, daß die ganze ganze "Kommunikation" vom ersten Versuch an einwandfrei funkltioniert hat (da hab' ich wohl Glück gehabt mit den download-Versionen und der Hardware...). Es geht also "nur" um's Verständnis und evtl. Änderungen gem. meinen Vorstellungen.

Es wäre besser, es sauber zu machen. Solltest du z.B. mal auf einen ESP32 o.ä. wechseln, wirst du einen Fehler beim Compilieren bekommen.

Zitat

In dem Prog ist auch noch 1 Befehl drin, der (zumindest für mich als C++ Anfänger) bemerkenswert ist: "radio.read(&got_Payload, sizeof(got_Payload) )" .
Mit dem &-Operator wird da auf got_Payload zugegriffen; das ist doch imho eine Zeiger-Operation, obwohl nirgendwo zuerst ein Zeiger deklariert wurde. Das gibt's wohl nur in C++ ....

Das ist simple C (C++) Pointer Arithmetik. Mit & bekommt man einfach die Adresse des angesprochenen Objekts. Dazu braucht man keinen expliziten Pointer. Allerdings ist das so IMHO falsch.

Die Deklaration ist: void read( void* buf, uint8_t len );

D.h. der erste Parameter ist ein Zeiger auf einen undefinierten Variablentyp - man kann eine Adresse einer Variable eines beliebigen Datentyps übergeben. Der Kommentar sagt: Pointer to the data to be sent.
D.h. er erwartet die Adresse des Feldes für die Empfangsdaten, in deinem Fall char got_Payload[34]
Allerdings ist got_Payload an sich schon ein Zeiger auf das char Feld. Also sollte reichen : radio.read(got_Payload, sizeof(got_Payload) );
Man kann auch schreiben &got_Payload[0]

RK