Problem: Ich habe mehrere Arrays, welche ich während der Laufzeit zu
einem Array zusammenfassen muss. Ich habe mir eine Funktion geschrieben
und wollte damit eigentlich einfach ein Array zurückgeben, dies
funktioniert aber so natürlich nicht.
Gibt es eine einfache Möglichkeit dies zu bedienen? Man Könnte ja noch
mit Pointern arbeiten, aber das wollte ich eigentlich umgehen.
Desweiteren: gibt es eine Möglichkeit ein Array nicht mit einer
statischen Größe anzulegen sondern mit einer dynamischen?
DFWKGF schrieb:> Sizeof macht nicht das was du denkst. Du solltest die größe der arrays> mit übergeben.
Ja ich weiß, das ist mir bewusst.
Bei uint8 aber schon, sizof gibt ja die Größe des Bereichs in Byte an...
das Array ist ja soundso viel Bytes groß, bei einem uint8_t Array[10]
ist die Größe auch 10. Bei einem uint16_t Array[10] ist die Größe dann
20 Bytes usw...
Und jetzt wo ich mir mal ein paar Zeilen mehr angesehen hab...
-newarray[] ist nach dem return tot
-arrays sind eigentlich auch pointer. Warum haben alle Anfänger Angst
vor Pointern?
DFWKGF schrieb:> Und jetzt wo ich mir mal ein paar Zeilen mehr angesehen hab...>> -newarray[] ist nach dem return tot> -arrays sind eigentlich auch pointer. Warum haben alle Anfänger Angst> vor Pointern?
- NewArray[] darf ja nach dem return tot sein. Es sei denn ich setze ein
Pointer.
- Die piecksen so :-D Das Problem ist, ich muss mich ja dann um die
ganze Speicherverwaltung selber kümmern, weil - wie du schon sagtest,
das neue Array dann ja tot ist, also muss ich es ja irgendwo speichern
können. Sobald ja der Pointer auf der Funkttion liegt, die Funktion
verlassen wird, wird ja der Speicher wieder freigegeben. Der Pointer
liegt zwar auf der Speicherstelle aber Werte darin können wieder
überschrieben werden.
Draco schrieb:> DFWKGF schrieb:>> Sizeof macht nicht das was du denkst. Du solltest die größe der arrays>> mit übergeben.>> Ja ich weiß, das ist mir bewusst.>> Bei uint8 aber schon, sizof gibt ja die Größe des Bereichs in Byte an...
nein macht es nicht. Scheinbar ist es dir nicht bewusst.
Draco schrieb:> Gibt es eine einfache Möglichkeit dies zu bedienen? Man Könnte ja noch> mit Pointern arbeiten, aber das wollte ich eigentlich umgehen.
Tust du aber bereits, offenbar ohne das zu wissen.
> Desweiteren: gibt es eine Möglichkeit ein Array nicht mit einer> statischen Größe anzulegen sondern mit einer dynamischen?
Ja.
> uint8_t[] AttachArray(uint8_t ArrayOne[],uint8_t ArrayTwo[])
Diese Zeile hätte schon einen Fehler bringen sollen, denn Arrays können
nicht als Rückgabetyp von Funktionen verwendet werden. Auch als
Parameter können sie nicht übergeben werden, aber da gibt es eine
Besonderheit: Es wird automatisch stattdessen ein Pointer auf den Anfang
des Arrays übergeben...
> {> uint8_t ArrayOneLength = sizeof(ArrayOne);> uint8_t ArrayTwoLength = sizeof(ArrayTwo);
... womit hier natürlich schon Blödsinn rauskommt, denn ArrayOneLength
und ArrayTwoLength wird die Größe eines Zeigers enthalten.
> uint8_t ArrayAllLength = ArrayOneLength + ArrayTwoLength;>> uint8_t newArray[];
Auch das wird nicht gehen. Mann kann keine Array-Variable ohne Größe
erzeugen.
> uint8_t NewArray[ArrayAllLength];
Das wiederum geht, da lokale Arrays auch eine erst zur Laufzeit
berechnete Länge haben. Allerdings hört dieses Array mit Rücksprung aus
der Funktion auf zu existieren, darf also danach nicht mehr benutzt
werden.
Rolf M. schrieb:>> uint8_t NewArray[ArrayAllLength];>> Das wiederum geht, da lokale Arrays auch eine erst zur Laufzeit> berechnete Länge haben.
ja und nein. Mittlerweile geht es, bei alten Compiler geht es nicht.
Keine Ahnung mit welcher C-Version es zulässig geworden ist.
Peter II schrieb:> ja und nein. Mittlerweile geht es, bei alten Compiler geht es nicht.> Keine Ahnung mit welcher C-Version es zulässig geworden ist.
Mit der, die im Jahre 1999 genormt wurde. Es stimmt allerdings, dass es
immer noch einige ewig gestrige Compiler-Hersteller gibt, die das selbst
stolze 17 Jahre später noch nicht hingekriegt haben.
>Desweiteren: gibt es eine Möglichkeit ein Array nicht mit einer>statischen Größe anzulegen sondern mit einer dynamischen?
Du kannst jederzeit ein bekanntes (typ) Array anlegen.
Sonst kannst Du nur einen, linear zusammenhängenden Bereich reservieren.
Die Nutzung desselben (Zeilen/Spalten) musst Du aber selber
dokumentieren.
Die Doppeldeutigkeit lässt sich einfach Aufzeigen:
Array 5X4 (20 Bytes)
Array 4X5 (20 Bytes)
Array 2X10 (20 Bytes)
Für weitere Arbeiten mit solch einem Block braucht’s die Info, wo z.B.
die Zweite Zeile und die Zweite Spalte liegen.
Zählung 1, 2, 3...
1. Array Byte 7
2. Array Byte 6
3. Array Byte 4
Traditionell lässt C nur die Rückgabe eines Wertes zu. Das kann ein Byte
sein, oder ein 64 Bit Zeiger.
Da ein Array meistens das gleiche ist, wie der Zeiger auf sein erstes
Element, kann man das ausnutzen, indem man nur das gesamte Array anlegt
und als Einzel-Arrays Pointer an die passende Stelle im Gesamt-Array
benutzt.
Dann stehen die Arrays automatisch hintereinander und AttachArray ist
überflüssig. Die einzelnen Funktionen, die mit den kleinen Arrays
arbeiten, merken das gar nicht: http://ideone.com/yv05vc
Sebastian S. schrieb:> Traditionell lässt C nur die Rückgabe eines Wertes zu. Das kann ein Byte> sein, oder ein 64 Bit Zeiger.
naja, es lässt auch eine stuct zu. Die wiederum mehre Werte haben kann.
Peter II schrieb:> Sebastian S. schrieb:>> Traditionell lässt C nur die Rückgabe eines Wertes zu. Das kann ein Byte>> sein, oder ein 64 Bit Zeiger.>> naja, es lässt auch eine stuct zu. Die wiederum mehre Werte haben kann.
Und die lustigerweise natürlich auch Arrays enthalten kann.
Tja, schade, das funktioniert nicht so wie du denkst.
Man kann in C keine Arrays als Parameter übergeben oder zurückgeben.
Im Prinzip ist das nur ne geschönte SChreibweise von
Und du siehst: es gibt keine Längeninformation, lediglich einen Pointer
auf das erste Element.
Das führt gleich zum zweiten Fehler:
1
uint8_tArrayOneLength=sizeof(ArrayOne);
2
uint8_tArrayTwoLength=sizeof(ArrayTwo);
Das hier kann aufgrund der Pointer, bzw. der fehlenden Lägneninformation
nicht klappen. Da kommt lediglich die Größe eines uint8_t*-Typs raus,
wahrscheinlich 2 (bei 16bit) oder 4, bzw. 8 (32bit/64bit).
Deswegen muss man die Arraylängen auch mitübergeben.
Somit enthält die nächste Zeile auch nicht die länge der kombinierten
Arrays, sondern einfach 2 x die Pointergröße
Und ein weiterer Fehler, der ganz böse enden kann (oder einfach nur
nicht funktioniert, das wäre der beste Fall):
1
uint8_tNewArray[ArrayAllLength];
Du legst hier ein Array mit dynamischer Größe an, das geht auch (imho
seit C99), soweit kein Problem (Das die Größe oben falsch berechnet
wurde ignorieren wir mal).
Dann wird kopiert.
1
for(uint8_ti=0;i<ArrayOneLength;i++)
2
NewArray[i]=ArrayOne[i];
Passt
1
for(uint8_ti=ArrayOneLength;i<ArrayAllLength;i++)
2
NewArray[i]=ArrayTwo[i-ArrayOneLength];
Ich hätte das eher so geschrieben: (Deins müsste aber auch gehen, habs
nur überflogen)
1
for(uint8_ti=0;i<ArrayTwoLength;i++)
2
NewArray[i+ArrayOneLength]=ArrayTwo[i];
So jetzt kommt der schlimme Fehler:
Davor hohle ich noch kurz aus:
1
uint8_tfoo(void){
2
//ein bisschen Code
3
{
4
uint8_tderAbsoluteWertDerZahlZweiUndVierzig=42;
5
}
6
returnderAbsoluteWertDerZahlZweiUndVierzig;
7
}
Was glaubst du passiert?
Ich hoffe man kann den Block {...} erkennen. Wenn innerhalb eines
Blockes (gleiches gilt für eine Funktion) eine Variable angelegt wird
(und Speicherplatz angefordert wird), dann lebt diese automatische
Variable nur solange, wie es den Block gibt. Danach wird sie "zerstört"
und der Wert (in diesem Speicherplatz) wird ungültig.
Zurück zu deiner Funktion:
Was machst du?
Genau, die erzeugst eine lokale (=automatische) Variable, die beim
Verlassen der Funktion wieder zerstört wird. Schlecht!
1
returnNewArray;
2
}
wird als einen Pointer zurückgeben, der auf einen undefinierten Bereich
zeigt.
Besser wäre es so (nur runtergetippt, enthält sicher Fehler!):
1
//gibt einen Pointer zum ersten Element des neuen Arrays zurück
2
void*AttachArray(void*newArray,//der User muss den neuen Speicherplatz beim Aufrufen bereitstellen
3
void*oldArray1,size_toldArray1_len,//das erste Array und seine Länge
4
void*oldArray2,size_toldArray2_len){//das zweite Array und seine Länge
Wie wäre es mit folgender Lösung? Ohne Pointer (bzw. ohne im Code
sichtbare Pointer) und mit vorallokiierten Speicherbereichen (buf), so
dass du dich zur Laufzeit nicht um dynamische Speicherverwaltung
(malloc, free) kümmern musst. Ob das die perfekte Lösung ist, mag ich
nicht zu beurteilen.
Damit hättest du ein uint8_t Array mit 10 Einträgen, das du alternativ
auch mit dynamic[0] bis dynamic[9] ansprechen kannst. Natürlich musst du
den zur Laufzeit reservierten Speicher bei Nichtnutzung wieder
freigeben, sonst müllst du dir deinen Heap zu.
Draco schrieb:> Gibt es eine einfache Möglichkeit dies zu bedienen? Man Könnte ja noch> mit Pointern arbeiten, aber das wollte ich eigentlich umgehen.> Desweiteren: gibt es eine Möglichkeit ein Array nicht mit einer> statischen Größe anzulegen sondern mit einer dynamischen?
Nur als schlechtes/akademisches Beispiel, liebe Kinder, bitte nicht
Nachmachen: C hat (über einen Trick) durchaus die Möglichkeit, Arrays
als Rückgabewert von Funktionen zu nutzen.
Man muss das Array lediglich in eine Struktur verpacken. Strukturen als
Rückgabewert sind erlaubt (aber nur in Ausnahmefällen empfehlenswert):
Markus F. schrieb:> Nur als schlechtes/akademisches Beispiel, liebe Kinder, bitte nicht> Nachmachen
Was ist denn der tiefere Sinn darin, im Detail und mit Beispiel zu
erklären, wie man es machen könnte, aber nicht soll?
Oh danke erstmal allen für eure sehr hilfreichen Antworten! Ich werde
mir das morgen alles mal zu Gemüte führen. Aber ich hab nun doch schon
einiges über Arrays gelernt - wo ich vorher einen anderen Blick drauf
hatte.
Ich probiere das morgen alles mal aus.
Rolf M. schrieb:> Markus F. schrieb:>> Nur als schlechtes/akademisches Beispiel, liebe Kinder, bitte nicht>> Nachmachen>> Was ist denn der tiefere Sinn darin, im Detail und mit Beispiel zu> erklären, wie man es machen könnte, aber nicht soll?
Weil das Beispiel für das konkrete Problem des TO zwar nicht passt, ich
aber trotzdem den Eindruck habe, daß dieses C-Feature (zu) oft vergessen
wird.
Viele (nicht nur angehende) Programmierer scheinen dem Eindruck zu
erliegen, daß C nur einfache Datentypen als Rückgabewert von Funktionen
erlaube. Ich habe schon mehrere Beispiele erlebt (die rauszusuchen bin
ich gerade zu faul) wo auch (anscheinend erfahrenere) Coder sofort
"Böse, Böse: return einer lokalen Variable" geschrien haben, wenn eine
Struktur returniert wurde.
Dabei ist das explizit erlaubt (die meisten Compiler haben sogar
effektive Mechanismen dafür, um den Stack nicht über Gebühr zu belasten)
und m.E. immer dann (insbesondere bei µC) besser, wenn die Alternative
dynamische Speicherverwaltung hieße. Man muß nicht immer mit Kanonen auf
Spatzen schießen.
Früher, damals im letzten Jahrtausend, als C noch jung und von K&R
bechrieben wurde, konnte man (auf manchen Systemen) keine structs
zurück- bzw. übergeben.
Aber seit C89 ist das geregelt.
Bezüglich sizeof: So, wie es der TO macht, geht es natürlich nicht, aber
prinzipiell ist sizeof sehr wohl in der Lage, die Größe eines Arrays zu
ermitteln (sogar die Anzahl der Elemente eines Arrays):
http://en.cppreference.com/w/c/language/sizeof
Gruß
Herby
Herbert P. schrieb:> Bezüglich sizeof: So, wie es der TO macht, geht es natürlich nicht, aber> prinzipiell ist sizeof sehr wohl in der Lage, die Größe eines Arrays zu> ermitteln (sogar die Anzahl der Elemente eines Arrays):>> http://en.cppreference.com/w/c/language/sizeof
aber nicht mehr in einer Funktion.
Hat aber nichts mit einer Funktion zu tun, sondern mit der Tatsache,
dass hier die Größe des Arrays dem Compiler nicht bekannnt ist. Sizeof
wird nicht zur Laufzeit ausgewertet, sondern bei der Übersetzung.
"sizeof(int[10])" funktioniert auch in einer Funktion.
Gruß
Herby
Herbert P. schrieb:> Hat aber nichts mit einer Funktion zu tun, sondern mit der> Tatsache,> dass hier die Größe des Arrays dem Compiler nicht bekannnt ist. Sizeof> wird nicht zur Laufzeit ausgewertet, sondern bei der Übersetzung.> "sizeof(int[10])" funktioniert auch in einer Funktion.>> Gruß> Herby
Nachtrag: in seltenen Ausnahmen auch zur Laufzeit (sizeof eines Arrays
mit variabler Größe)
Herby
Wo wir dann gerade dabei sind, wie ermittel ich denn ein Array zur
Laufzeit? Oder ist es sinnvoller eine Variable mitlaufen zu lassen in
welcher ich die größe des Arrays von vornherein Speicher?!
Draco schrieb:> Wo wir dann gerade dabei sind, wie ermittel ich denn ein Array zur> Laufzeit? Oder ist es sinnvoller eine Variable mitlaufen zu lassen in> welcher ich die größe des Arrays von vornherein Speicher?!http://en.cppreference.com/w/c/language/array
Gruß
Herby
Draco schrieb:
> Wo wir dann gerade dabei sind, wie ermittel ich denn ein Array zur> Laufzeit? Oder ist es sinnvoller eine Variable mitlaufen zu lassen in> welcher ich die größe des Arrays von vornherein Speicher?!
Unbedingt.
Wo wir dann gerade dabei sind, wie ermittel ich denn ein Array zur
Laufzeit? Oder ist es sinnvoller eine Variable mitlaufen zu lassen in
welcher ich die größe des Arrays von vornherein Speicher?!
Herbert P. schrieb:> http://en.cppreference.com/w/c/language/array>> Gruß> Herby
Da wird auch ausschließlich mit sizeof gearbeitet. Also so verkehrt kann
das ganricht sein, solange es nicht funktionsübergreifend ist.
Dirk B. schrieb:> Draco schrieb:>> Wo wir dann gerade dabei sind, wie ermittel ich denn ein Array zur>> Laufzeit? Oder ist es sinnvoller eine Variable mitlaufen zu lassen in>> welcher ich die größe des Arrays von vornherein Speicher?!>> Unbedingt.
Ich weiß nun nicht ob das Sarkasmus oder Ernst ist :-D Ich gehe mal von
ernst aus. Es ist ja nun auch nicht unbedingt üblich die Länge während
einer Laufzeit zu ermitteln, da man ja eigentlich die Längen immer im
Kopf hat. Deswegen kann man ja den Umweg für Ausnahmefälle die Länge
eines Arrays in einer Variable speichern, oder ist das schlechter Stil?!
Das war ernst gemeint.
Spätestens in einer Funktion hast du keine implizite Längeninformation
über das Array.
Und eigentlich sollte es dem Programm egal sein, ob du ein Array hast
oder dynamischen Speicher.
strcpy, strcat, strncpy und strncat sind Beispiele dafür, wie man es
nicht macht.
Markus F. schrieb:> Dabei ist das explizit erlaubt (die meisten Compiler haben sogar> effektive Mechanismen dafür, um den Stack nicht über Gebühr zu belasten)> und m.E. immer dann (insbesondere bei µC) besser, wenn die Alternative> dynamische Speicherverwaltung hieße. Man muß nicht immer mit Kanonen auf> Spatzen schießen.
Die gewählte Alternative ist in C aber meist eher, den Aufrufer sich um
den Speicher kümmern zu lassen und einen Zeiger darauf reinzugeben.
Herbert P. schrieb:> Hat aber nichts mit einer Funktion zu tun, sondern mit der Tatsache,> dass hier die Größe des Arrays dem Compiler nicht bekannnt ist.
Es hat nichts mit bekannten oder unbekannten Arraay-Größen zu tun,
sondern damit, dass das, was hier an sizeof übergeben wird, schlicht und
ergreifend kein Array ist, sondern ein Zeiger. Es gibt zwar hier wieder
einige, die behaupten, ein Array sei ein Zeiger, aber das ist falsch.
Und sizeof ist eine der Stellen, an denen man das merkt.
Draco schrieb:> Wo wir dann gerade dabei sind, wie ermittel ich denn ein Array zur> Laufzeit?
Gar nicht.
> Oder ist es sinnvoller eine Variable mitlaufen zu lassen in welcher ich> die größe des Arrays von vornherein Speicher?!
Du musst dich selbst drum kümmern, die Größe zu kennen, z.B. indem du
sie irgendwo mitführst, oder wie z.B. bei C-Strings durch Nutzung eines
bestimmten Wertes als Ende-Markierung.
Wenn du z.B. einen char* hast, ist das ein Zeiger auf einen char,
nicht mehr und nicht weniger. Ob da im Speicher jetzt genau nur dieser
einzelne char steht oder dieser nur der Anfang eines ganzen Arrays ist,
läßt sich am Zeiger nicht erkennen.
Draco,
ich kann kaum C, programmiere meist immer noch in VBA.
die optimale Lösung für Dein Problem hängt davon ab, wie Du Deine Arrays
überhaupt nutzt!
Ist es so, dass Deine Arrays immer nur nach hinten anwachsen und dann
zwei Arrays aneinander gehängt werden müssen?
Dann kannst Du Dein Problem z.B. mit linearen Listen erschlagen:
http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/...
Du reservierst Dir den Speicher für jedes Element dynamisch und hängst
die neuen Elemente der Reihe nach hinten an.
Wenn Du zwei Listen verketten willst, durchläufst Du die vordere bis zum
letzten Element und setzt den Zeiger auf das nächste Element auf das
erste Element der hinteren Liste.
Das birgt einen Nachteil:
Bei jedem Dranhängen musst Du die Liste linear bis zum Ende durchlaufen,
so auch, wenn Du zwei Listen verketten willst.
Das kannst Du mit einem Trick umgehen:
Du nutzt die Draco-Liste!
Deine Liste besteht vorne aus dem besonderen Draco-Struktur, der normale
Structuren folgen.
Die Draco-Struktur hat zwei Zweiger:
Der eine Zeiger zeigt auf das letzte Element der Liste, und der zweite
Zeiger auf das nächste Element der Liste.
Das Anhängen von neuen Elementen geht jetzt ganz schnell:
Mit der Draco-Struktur vorne findest Du in einem Schritt das letzte
Element Deiner Liste und hängst einfach etwas an. Natürlich musst Du
auch den zweiten Zeiger vorne pflegen, damit der wieder auf das letzte
Element der Liste zeigt.
Wenn Du die zweite Draco-Liste dranhängst, musst Du beim Verketten nur
darauf achten, dass das letzte Element der ersten Liste nicht auf die
Drako-Struktur zu Beginn der zweiten Liste zeigt, sondern auf das erste
Element der zweiten Liste.
Den Speicher für die Drako-Struktur der zweiten Liste solltest Du dann
vermutlich freigeben, wenn Du sie nicht mehr brauchst.
Wenn Du allerdings in Deinen Listen öfters Elemente löschst und neue
irgendwo einfügst, dann bieten sich Baumstrukturen zur Verwaltung an.
Die sind allerdings ein bischen kniffeliger.
Hier ein Musterbeispiel für wirklich ineffizienten Code, wo jemand
Arrays verwendet, die aber ungeeignet sind.
Das vorhandene Array wird zyklisch in ein größeres Array umkopiert:
http://www.vb-fun.de/cgi-bin/loadframe.pl?ID=vb/ti...
Das Umkopieren bremst die rekursive Dateisuche enorm aus.
Loggt man das Anwachsen des Dateizählers "m_lngFileCount" mit, kann man
zugucken, wie das Programm immer langsamer wird.
Das kann man in VBA ganz geschickt durch Wahl der passenden
Datenstruktur (kein Array!) vermeiden.
Es ist keine gute Idee, Strategien mit Konstrukten, die in Sprachen wie
VB oder VBA irgendwie funktionieren, auf C zu übertragen.
Große dynamische Strukturen verwaltet man am besten mit Zeigern.
Man denkt sich eine Struktur aus, die die Daten tragen soll.
Dynamisch reserviert man sich Speicher für so ein Element der Struktur,
packt seine Daten herein und sortiert das Element in seine Struktur ein
- via Zeiger. Beim Löschen repariert man seine Struktur und gibt den
freigewordenen Platz frei.
Irgendwann landet man dann auch bei Baumstrukturen.
Vom Array hin zu einer zeigerbasierten Struktur ist ein wichtiger
Erkenntnissprung, an dem kommt man irgendwann nicht mehr vorbei.
Möge der Zeiger bald mit Dir sein. :)
Viel Erfolg!