Arduino: statisches Array einmalig dynamisch anlegen und füllen. WIE?

#1

Hallo zusammen,

ich bastel gerade an einen Arduino (https://de.wikipedia.org/wiki/Arduino_(Plattform)) Microcontroller-Projekt, wo ich eine bestehende Bibliothek etwas umbauen/vereinfachen will.
Aber irgendwie hapert’s an meinen C++ Kenntnissen…

Kurz in Textform erklärt:

Ich habe eine Klasse die ein statisches Array anbietet. Die Library gibt vor, dass das Array vom Benutzer initialisiert und gefüllt werden soll.

In etwa so:

 MyObject(...),
 MyObject(...),
 MyObject(...)
}

Das klappt soweit (weil: wurde nicht von mir gebaut :D).
Mein Ziel ist es jetzt, dass ich in das Array an Index=0 ein vordefinirrtes Objekt ablege, und der User sich nur noch um den restlichen Inhalt bemühen muss. Er soll von den vordefinierten Objekt an Index=0 aber eigentlich möglichst wenig sehen/mitbekommen.

Deshalb mein Ansatz:
Den User ein eigenes Array mit seinen Objekten erstellen lassen (also nicht das statische Nutzen) und dieses Array dann in einen sowieso notwendigen Methodenaufruf mit reinpacken.
In diesem Methodenaufruf wird das eigentlich vorgesehene statische Array mit der passenden Größe für “User-Objekte + Spezial-Objekt” erzeugt, an Index=0 das Spezialobjekt eingefügt und den Inhalt vom User-Array in das statische Array umkopiert.
Aber ich scheitere bereits am anlegen/initialisieren des statischen Arrays.

Ich hab das hier mal versucht in einem kurzen Code-Beispiel zu demonstrieren:

Datei A.* beherbergt das statische Array
Datei B.* beherbergt die Methode die das User-Array entgegen nimmt und dannd das statische Array initialisiert und befüllt

Datei A.h:

class A {
...
public:
		static MyObject _myObjectList[];	
...
}

Datei A.cpp:

include "A.h"
class A {
/* Verwendet überall und ständig "_myObjectList" im Stil von
 *		_myObjectList**.GetSomething()
 */
}

Datei B.h

class B {
...
	public:
		void init(..., MyObject anotherMyObjectList, ....);
	private:
		MyObject createSpecialMyObject();
...
}

Datei B.cpp

include "A.h"
include "B.h"

B:B() {

	void B::init(..., MyObject anotherMyObjectList, ...) {

		// ermittle Array-Größe von anotherMyObjectList
		int len = sizeof(anotherMyObjectList) / sizeof(MyObject);
		
		// initialisiere A::_myObjectList[] für Arraygröße "len+1"
		// ---> ???
		// bisheriger kläglicher Versuch:
		A::_myObjectList = (MyObject*) malloc(sizeof(MyObject)*(len+1));
		//
		// Fehler: "invalid use of array with unspecified bounds"
		// Zusätzlich bekomm ich noch den Fehler in/für A.h:
		// error: "A::_myObjectList" has incomplete type. ----> Kann ich mir noch weniger erklären...
		
		// platziere ein "spezielles" MyObject an Index 0
		A::_myObjectList[0] = createSpecialMyObject();
		
		// füge Inhalt von anotherMyObjectList danach ein
		for(int i=1; i<(len+1); i++) {
			A::_myObjectList** = anotherMyObjectList[i-1];
		}
	}	
	
	MyObject B::createSpecialMyObject() {
		MyObject m = MyObject(....);
		m.SetSomething(...);
		m.SetSomethingElse(...);
		return m;
	}

}

Rahmenbedingungen:

Die Definition des statsichen Arrays in A.h muss bleiben wie sie ist, bzw. darf sich nicht soweit ändern, dass bisherige Zugriffe angepasst/geändert werden müssen (das wäre zuuuu viel arbeit das alles umzubauen).

Kann mir da jemand unter die Arme greifen?
Kann doch nicht so schwer sein. In Java hätte ich das in nichtmal einer Minute gelöst. Mit C++ mach ich jetzt schon 2 Tage rum und kriegs nicht gebacken. Aber vielleicht seh ich mal wieder den Wald vor lauter Bäumen nicht.

Gruß
Alex

#2

Sooooo,

Welche Bibliothek??

Das wird dort nämlich nicht so drinnen stehen

class Demo {
public:
	int v1;
	int v2;
};
int main() {
        // in Klasse A
	Demo d3[];
        // irgendwo ein Zugriff darauf
	d3[0] = Demo();
}

liefert nämlich

mogel@lucretia:~/temp$ g++ demo.cpp && ./a.out
demo.cpp: In function ‘int main()’:
demo.cpp:22:10: error: storage size of ‘d3’ isn’t known
  Demo d3[];
          ^
[...]
mogel@lucretia:~/temp$

Die Definition würde also schon mal gar nicht so einfach funktionieren.

Kurzer Exkurs: verwende bitte den new-Operator - malloc ist C. Auch wenn new durchaus am Ende mit malloc arbeitet. Du weist aber nicht in wie weit der new-Operator von Deiner Entwicklungsumgebung geändert wurde bzw. was im Hintergrund noch alles passiert. Zumindest schmeißt new() eine bad_alloc Exception wenn der Speicher voll ist. Bei malloc() musst Du den Rückgabewert auf NULL prüfen


A::_myObjectList = (MyObject*) malloc(sizeof(MyObject)*(len+1));
// wird dann (theoretisch) zu
A::_myObjectList = *( new MyObject[len] );


Demo d2[] = { Demo(), Demo() };
int len = 3;
d2 = *( new Demo[len] );

// ...

demo.cpp:26:5: error: incompatible types in assignment of ‘Demo’ to ‘Demo [2]’
  d2 = *( new Demo[len] );

Ohne die bestehende Bibliothek ist da erst mal nicht viel zu machen.


    MyObject B::createSpecialMyObject() {
        MyObject m = MyObject(....);
        m.SetSomething(...);
        m.SetSomethingElse(...);
        return m;
    }

Ist das von Dir so gewollte das hier der Copy-Constructor verwendet werden soll? Auf embedded Geräten würde ich um den Copy-Construktor einen riesen Bogen machen. Das Objekt wird hier auf dem Stack erzeugt und bei der Rückgabe werden die Werte in ein neues Objekt kopiert, was dann an anderer Stelle auf dem Stack liegt oder dann (endlich) im Heap (je nach vorhandenem Quellcode).

#3

Welche Bibliothek??

Das ist die Ausgangsbasis: https://github.com/KONNEKTING/KonnektingDeviceLibrary

Das wird dort nämlich nicht so drinnen stehen

Doch, tut es:

https://github.com/KONNEKTING/KonnektingDeviceLibrary/blob/master/KnxDevice.h#L112

Und das compiliert prinzipiell auch und funktioniert auf dem Arduino bestens.

liefert nämlich

Dein Beispiel: Ja.
Die Lib: Nein.

Denn wie ich geschrieben hatte, wird das Array bis dato folgendermaßen initialisiert:

 MyObject(...),
 MyObject(...),
 MyObject(...)
}```

Nicht in der main() (weil die gibt es beim Arduino nicht), sondern außerhalb von setup() und loop():

https://github.com/KONNEKTING/KonnektingDeviceLibrary/blob/master/examples/Demo_Sketch_Konnekting/Demo_Sketch_Konnekting.ino#L35

Und das funktionier bisher, wie ich bereits geschrieben hatte, bestens.


> Die Definition würde also schon mal gar nicht so einfach funktionieren.


In deiner konstellation nicht, nein. Aber im Umfeld des Arduino in gezeigter Konstellation schon.

```A::_myObjectList = (MyObject*) malloc(sizeof(MyObject)*(len+1));
// wird dann (theoretisch) zu
A::_myObjectList = *( new MyObject[len] );```

ich experimentier mal damit. Danke.


> Ist das von Dir so gewollte das hier der Copy-Constructor verwendet werden soll? Auf embedded Geräten würde ich um den Copy-Construktor einen riesen Bogen machen. Das Objekt wird hier auf dem Stack erzeugt und bei der Rückgabe werden die Werte in ein neues Objekt kopiert, was dann an anderer Stelle auf dem Stack liegt oder dann (endlich) im Heap (je nach vorhandenem Quellcode).


Wenn ich das so lese, dann wundere ich mich immer mehr dass ich mit dem Umbau der Lib schon so weit gekommen bin. Ich hab keinen Schimmer was ein Copy-Constructor ist :-( Und in der Arduino-Welt gibt es jede Menge tutorials die nicht so ins Detail gehen :-( ).
Interessanterweise liest man da aber immer wieder: "new" geht nicht bei Arduino (was so aber wohl nicht ganz stimmt) und "mach's nicht mit new" ... Was ich aber bis dato noch nicht wirklich begründet gesehen habe. 


Auf was ich letztendlich hinaus will:

Dieses Beispielprogramm https://github.com/KONNEKTING/KonnektingDeviceLibrary/blob/master/examples/Demo_Sketch_Konnekting/Demo_Sketch_Konnekting.ino#L35 initialisiert in Zeile 35 das Array. Der benutzer muss immer an Index=0 (Zeile 36) diese spezielle Methode aufrufen um ein spezielles Objekt an index=0 abzulegen. Das soll künftig intern passieren.

*** Edit ***

[QUOTE=tuxedo]

```A::_myObjectList = (MyObject*) malloc(sizeof(MyObject)*(len+1));
// wird dann (theoretisch) zu
A::_myObjectList = *( new MyObject[len] );```

ich experimentier mal damit. Danke.
[/QUOTE]

:-( Funktioniert nicht wirklich:





> KnxDevice.cpp:68:46: error: no matching function for call to 'KnxComObject::KnxComObject()'
>      _comObjectsList = *( new KnxComObject[len] );




Aus der KnxDevice.h:


public:

// List of Com Objects attached to the KNX Device
// The definition shall be provided by the end-user
static KnxComObject _comObjectsList[];  


Wird da versucht den Konstruktor aufzurufen? Wenn ja:

* Wieso? Ich will an der Stelle keine Instanz ins leben rufen?
* Es gibt keinen argumentlosen Konstruktor für KnxComObject




[update]

--> [programming - How can I declare an array of variable size (Globally) - Arduino Stack Exchange](http://arduino.stackexchange.com/questions/3774/how-can-i-declare-an-array-of-variable-size-globally)


> 2/ How can I have an array which size is dynamic (i.e. not known until runtime)?


In beiden vorgestellten Fällen ( C / C++ ) ist die Array-Deklaration im Stil:

int* myArray = 0;


Die Lib gibt aber an der Stelle ein

static KnxComObject _comObjectsList[];```

vor. Riecht aktuell danach, als ob ich damit nicht weit komme?!

Und wenn ich’s auf

static KnxComObject* _comObjectsList;

ändere, dann hab ich mind 20 Stellen im Code die sich darüber beschweren, was nach einem größeren Umbau klingt.

#4

Moin,

ich habe mir die Library mal angeschaut. Das was Du willst, wird nicht funktionieren.

static KnxComObject _comObjectsList[];

damit weis der Compiler nur das es irgendwo ein festes Array vom Type und dem Namen gibt, mehr noch nicht. Irgend wann stolpert der Compiler über


KnxComObject KnxDevice::_comObjectsList[] = {
    /* don't change this */ Tools.createProgComObject(),
                            
    // Currently, Sketch Index and Suite Index differ for ComObjects :-(
                            
    /* Sketch-Index 1, Suite-Index 0 : */ KnxComObject(KNX_DPT_1_001, COM_OBJ_LOGIC_IN),
    /* Sketch-Index 2, Suite-Index 1 : */ KnxComObject(KNX_DPT_1_001, COM_OBJ_SENSOR),
};

nun weis der Compiler wie groß dieses feste Array ist, der Linker weis anschließend wo es steht. Alles passiert noch zu Compilerzeit - zur Laufzeit lässt es sich nicht mehr ändern! Wenn Du es zur Laufzeit ändern willst, wirst Du um Pointer auf Pointer nicht herum kommen.

static KnxComObject** _comObjectsList;

Sonstiges


const byte KnxDevice::_numberOfComObjects = sizeof (_comObjectsList) / sizeof (KnxComObject);

Ich bin persönlich kein Freund von solchen Initialisierungen. Die Initialisierung selber ist in dem Fall Deterministisch, allerdings ist hier die Frage ob _comObjectsList bereits initialisiert ist. Die Antwort selber kann nur der Compiler/Linker liefern. Meistens funktioniert es. Das Problem ist eigentlich eher das die Initialisierung auch als Methodenaufruf geschrieben werden kann bzw. das Daten aus anderen CPP verwendet werden können.


#include <iostream>
using namespace std;
int func1();
int func2();
int value1 = func1();
int value2 = func2();
int func1() {
	cout << "init value1" << endl;
	return 2;
}
int func2() {
	cout << "init value2" << endl;
	return 4;
}
int main() {
	cout << "Value1: " << value1 << endl;
	cout << "Value2: " << value2 << endl;
}
// Output
mogel@lucretia:~/temp$ g++ demo.cpp && ./a.out
init value1
init value2
Value1: 2
Value2: 4

In den Funktionen kann auch ein Methodenaufruf aus andere.cpp oder Objekte verwendet werden, welche evt. noch nicht initialisiert sind. Das knallt dann und man sucht sich einen Wolf, da noch nicht mal die main() Methode aufgerufen wird.

kennt Java aber auch Java Practices -> Copy constructors

Ist ja auch kein Arduino Problem sondern ein C/C++ Problem. In den Tutorials wird davon ausgegeangen das man schon grundlegende Ahnung von C/C++ hat.

Da fehlt wahrscheinlich nur der entsprechende new-Operator. Ich hatte das gleiche Problem bei meinem Atmel-Board. Nachdem ich den new-Operator selber (mittels malloc) implementiert hatte, funktionierte auch new (operator new, operator new[] - cppreference.com).


#include <cstdio>
#include <cstdlib>
// replacement of a minimal set of functions:
void* operator new(std::size_t sz) {
    std::printf("global op new called, size = %zu
",sz);
    return std::malloc(sz);
}
void operator delete(void* ptr) noexcept
{
    std::puts("global op delete called");
    std::free(ptr);
}

#5

Das Thema hat sich bis auf weiteres erledigt. Hab jetzt erst festgestellt dass, selbst wennd as mit dem Array nun klappt, ich an anderer Stelle ein konzeptionelles Problem damit habe. Muss das Thema also komplett anders lösen.