Forum: Mikrocontroller und Digitale Elektronik struct: mehrere variablen erstellen


von Bernd S. (mms)


Lesenswert?

Hallo,

eine Frage zu einer struct...
1
typedef struct data {
2
  
3
  volatile unsigned int *p_DataBuf;              
4
  volatile unsigned int HdrNbr;              
5
  
6
}*p_Rx;

in meiner C-Datei erstelle ich mir davon eine Variable
1
p_Rx p_RxHdr;
1
for (i=0; i < 8; i++) {    
2
3
    p_RxHdr[i].p_DataBuf = NULL;     
4
    p_RxHdr[i].HdrNbr= i; 
5
    
6
  }

Im Prinzip hab ich 8 Datensätze von diesem Struct Data und möchte diese 
in dieser for-Schleife mit den Ausgangswerten initialisieren. Kann man 
das so machen?

Ist das das gleiche, als wenn ich vom Typ data mehrere Variablen 
deklariere?
1
p_Rx p_RxHdr, p_RxHdr2, p_RxHdr3, p_RxHdr4;


Gruß
Bernd

von Uhu U. (uhu)


Lesenswert?

Versuchs mal so:
1
struct data p_RxHdr[4];
2
3
for (i=0; i < 8; i++) {    
4
5
    p_RxHdr[i].p_DataBuf = NULL;     
6
    p_RxHdr[i].HdrNbr= i; 
7
    
8
  }

von Bernd S. (mms)


Lesenswert?

in meiner version bekomm ich keinerlei fehlermeldungen angezeigt, nur 
bin ich mir nicht ganz sicher ob in der for-schleife tatsächlich mehrere 
variablen davon erzeugt werden bzw. ein array.

Bernd

von Uhu U. (uhu)


Lesenswert?

p_Rx p_RxHdr;

erzeugt garkeine Variable vom Typ struct data, sondern einen Pointer 
darauf. Da du den Pointer noch nicht einmal initialisierst, wird deine 
for-Schleife höchstwahrscheinlich beim ersten Zugriff über p_RxHdr 
abrauchen.

Der Compiler prüft nur, ob das, was du schreibst, syntaktisch richtig 
ist; wenn er keinen Fehler meldet, heißt das noch lange nicht, daß der 
Quelltext nicht völliger Unsinn ist.

Der Satz: "Draußen ist es kälter, als morgen." ist syntaktisch korrektes 
Deutsch, aber semantisch Unsinn...

von Bernd S. (mms)


Lesenswert?

d.h. in der for-schleife erzeuge ich einfach mehrere zeiger (p__RxHdr0, 
p__RxHdr1 usw. ), die alle auf die gleiche struktur data zeigen, aber 
keine variablen von der struktur data selbst sind.

>Da du den Pointer noch nicht einmal initialisierst

ok - der pointer müsste dann noch intialisiert werden mit einer 
Speicheradresse.


Bernd

von Oops (Gast)


Lesenswert?

Hi,

wenn ich da mal was ergänzen darf:

Das typedef deklariert einen Typen namens p_Rx als Zeiger auf eine 
Struktur namens Data.

Mit p_Rx p_RxHdr; wird ein Zeiger auf solche eine Struktur alloziiert,
aber nicht, wie Uhu Uhuhu schon richtig sagt, Speicherplatz für die 
Struktur.

Viele Compiler gucken nicht darauf, ob der Speicherplatz, auf den 
zugegriffen wird auch tatsächlich alloziiert ist. Teilweise liegt das 
daran, das angenommen wird, man könne nicht wissen, was über den Linker 
noch dazu kommt.
Das könnte beispielsweise eine Dekklaration oder ein alloc sein.
Andere Tools, wie Lint warnen aber vor solchen Fällen.

Ich denke der Knackpunkt liegt hier darin, das man strikt darauf achten 
muss, das Typedef einen Typnamen deklariert, keine Variablen. Dein * vor 
dem p_Rx gehört also dem Sinn nach zu struct data.

Gruss
Oops

von Uhu U. (uhu)


Lesenswert?

Bernd Schuster wrote:
> d.h. in der for-schleife erzeuge ich einfach mehrere zeiger (p__RxHdr0,
> p__RxHdr1 usw. ), die alle auf die gleiche struktur data zeigen, aber
> keine variablen von der struktur data selbst sind.

Die for-Schleife erzeugt nicht mehrere Pointer, sondern sie benutzt nur 
den einen, den du definiert hast, und macht damit Adressarithmetik.

Du solltest mal in deinem C-Buch die Kapitel über Pointer, Arrays und 
Adressarithmetik nachlesen.

von Bernd S. (mms)


Lesenswert?

hab mir ein bsp von atmel angeschaut, in der die das so realisiert haben 
(ich hoffe ich hab keinen wichtigen code-teil vergessen).

Einfach um zu sehen, warum das hier so geht und es keine 
Speicherprobleme etc. gibt:

das ist deren struct:
1
typedef struct _AT91S_TdDescriptor {
2
  unsigned int addr;
3
  unsigned int size;
4
}AT91S_TdDescriptor, *AT91PS_TdDescriptor;

ein ptr auf die struct:
1
AT91PS_TdDescriptor tdList = (AT91PS_TdDescriptor)AT91C_EMAC_TDLIST_BASE;

wobei dieser word aligned sein muss...
1
#define AT91C_EMAC_TDLIST_BASE  0x20010000

und anschließend greifen sie auf die struct so zu:
1
for (i = 0; i < 8; ++i) 
2
  {
3
    tdList[i].addr = ((unsigned int) ((i * ETH_PACKET_SIZE))) & 0xFFFFFFFC;
4
    tdList[i].size = 0;
5
  }


.addr beinhaltet z.b. die Daten, die über die Ethernet Schnittstelle an 
den µController kommen. Und .size nur die Information ob dieser Buffer 
leer oder voll ist.

Ist das gut programmiert - oder könnte diese Herangehensweise zu 
Problemen bezüglich Speicherzugriffsfehler etc. führen? Weil eine 
Variable wird von der struct auch nicht erzeugt, so dass für diese 
Speicher alloziiert werdne könnte?

Bernd

von Uhu U. (uhu)


Lesenswert?

Sowas benutzt man nur für Datenbereiche, deren Adresse von der Hardware 
vorgegeben ist.

Für normale Variable überläßt man dem Compiler die Adressraumverwaltung, 
denn das Programm würde sonst sehr schnell unpflegbar.

von Oops (Gast)


Lesenswert?

Hi Bernd.

Ich setze mal voraus, das der gesamte Code keinen Fehler enthält.
Dann ist Dein Code-Teil unvollständig. Denn es fehlt wie gesagt, die 
Definition des Speichers an der Address 0x0x20010000.

Ich vermute mal es handelt sich hier um einen Memory mapped I/O für 
Ethernet.
Dann wird irgendwo in den Projektdateien die Definition enthalten sein.

Gruss
Oops

von Uhu U. (uhu)


Lesenswert?

@ Oops

Bernd hat die Definition angegeben:

#define AT91C_EMAC_TDLIST_BASE  0x20010000

von Bernd S. (mms)


Lesenswert?

>Denn es fehlt wie gesagt, die Definition des Speichers an der Address 
>0x0x20010000

dass ist eine speicheradresse, die im internen RAM oder externen SDRAM 
(wie in diesem Fall) liegen kann. Dorthin kommen die Daten von der 
Ethernet Schnittstelle. Sprich diese Adresse ist nicht vorgegeben von 
der Hardware.

>Sowas benutzt man nur für Datenbereiche, deren Adresse von der Hardware
>vorgegeben ist.

d.h. hier muss ich nicht zuvor speicher reservieren?

Bernd

von Uhu U. (uhu)


Lesenswert?

>>Sowas benutzt man nur für Datenbereiche, deren Adresse von der Hardware
>>vorgegeben ist.
>
> d.h. hier muss ich nicht zuvor speicher reservieren?

Ja. Der Speicher muß vorhanden sein und du solltest die Methode als den 
Sonderfall betrachten, der es ist.

von Oops (Gast)


Lesenswert?

@ Uhu Uhuhu
>Bernd hat die Definition angegeben:

>#define AT91C_EMAC_TDLIST_BASE  0x20010000

Tut mir leid, aber obige Anweisung wird vom Präprozessor interpretiert 
und nicht vom Compiler. Schon deswegen ist das keine Definition die 
Speicherplatz reserviert. Es ist ein Präprozessor-Definition.

Falls Du aber meintest, das er "uns" (als Forumsleser) damit mitgeteilt 
hat wo der Speicher liegt, liegen könnte oder liegen sollte, hast Du 
natürlich recht. Aber der Compiler muss das wissen, nicht wir.

Gruss

Oops

von Uhu U. (uhu)


Lesenswert?

Bernd:
> ein ptr auf die struct:
>
> AT91PS_TdDescriptor tdList = (AT91PS_TdDescriptor)AT91C_EMAC_TDLIST_BASE;
>
> wobei dieser word aligned sein muss...
>
> #define AT91C_EMAC_TDLIST_BASE  0x20010000

Da stehts doch - ist zwar in umgekehrter Reihenfolge, aber es ist ja 
auch kein Programm, sondern eine verbale Beschreibung des Codes...

von Bernd S. (mms)


Lesenswert?

wie kann man denn für einen bestimmten bereich speicher alloziieren, 
wenn die Pakete an die Speicherstelle 0x20010000 ff. geliefert werden?

Ich weiß wie groß der Speicherbereich sein wird und ab welcher Adresse 
er anfängt`.

malloc ist mir ein Begriff.

Bernd

von Uhu U. (uhu)


Lesenswert?

Bernd Schuster wrote:
> malloc ist mir ein Begriff.

Damit nicht.

Du has oben schon hingeschrieben, wie man das macht:
1
   #define AT91C_EMAC_TDLIST_BASE  0x20010000
2
   AT91PS_TdDescriptor tdList = (AT91PS_TdDescriptor)AT91C_EMAC_TDLIST_BASE;

Das #define definiert die Adresse.

   (AT91PS_TdDescriptor)AT91C_EMAC_TDLIST_BASE

casted die Konstante in einen AT91PS_TdDescriptor.
Damit zeigt tdList auf die Adresse 0x20010000

Ohne Umweg über das #define - was man aber tunlichst nicht machen soll - 
sieht es so aus:
1
   AT91PS_TdDescriptor tdList = (AT91PS_TdDescriptor) 0x20010000;

oder
1
   AT91S_TdDescriptor *tdList = (AT91S_TdDescriptor *) 0x20010000;

von Bernd S. (mms)


Lesenswert?

aber damit definiere ich ja nur den startpunkt im speicher und nicht den 
ganzen speicherbereich, oder?

Bernd

von Oops (Gast)


Lesenswert?

@ Bernd:

Kannst Du bitte mal einen Link posten, wo der Quellcode im Netz zu sehen 
ist? Oder ist das eine Eigenentwicklung?

Gruss

Oops

von Oops (Gast)


Lesenswert?

@Bernd

>aber damit definiere ich ja nur den startpunkt im speicher und nicht den
>ganzen speicherbereich, oder?

Genau!

Gruss
Oops

von Oops (Gast)


Lesenswert?

@ Bernd:

Ich schrub:
"Dann wird irgendwo in den Projektdateien die Definition enthalten 
sein."

Gruss
Oops

von Bernd S. (mms)


Lesenswert?

der ursprüngliche Code ist hier:
http://lxr.post-tech.com/source/cpu/at91rm9200/at91rm9200_ether.c?v=mv2440_uboot_rel1.1;a=ppc

(allerdings andere Namensgebung etc. RBF_FRAMEMEM ist die Startadresse 
und rbf_t der Zeiger von der Struktur

Bernd

von Uhu U. (uhu)


Lesenswert?

Bernd Schuster wrote:
> aber damit definiere ich ja nur den startpunkt im speicher und nicht den
> ganzen speicherbereich, oder?

Jain. Du 'behauptest', daß auf der Adresse AT91C_EMAC_TDLIST_BASE ein 
Objekt vom Typ AT91S_TdDescriptor liegt. Damit has du implizit natürlich 
eine Längenangabe, nämlich sizeof (AT91S_TdDescriptor).

Wegen der Strukturähnlichkeit von Array und Pointer kannst du relativ zu 
deinem Pointer ein ganzes Array von AT91S_TdDescriptor-Objekten 
adressieren, z.B. so:
1
   *tdList         // dereferenziert das Objekt auf tdList
2
   tdList[0]       // dto.
3
4
   *(tdList + 3)   // dereferenziert das 4. Objekt ab tdList
5
   tdList[3]       // dto.
6
7
   *(tdList - 5)   // dereferenziert das 5. Objekt unterhalb tdList
8
   tdList[-5]      // dto.

Dem Compiler ist dabei egal, ob auf den angesprochenen Adressen Speicher 
liegt, oder nicht - deswegen meine Formulierung: "du 'behauptest'". Die 
Längenangabe relativiert sich damit wieder sehr...

> Du solltest mal in deinem C-Buch die Kapitel über Pointer, Arrays und
> Adressarithmetik nachlesen.

Solltest du unbedingt tun!!!

von Oops (Gast)


Lesenswert?

@ Bernd

Dankeschön. Interessant.

Gruss
Oops

von Oops (Gast)


Lesenswert?

@Bernd

Na, alle Klarheiten beseitigt?

Gruss

Oops

von Bernd S. (mms)


Lesenswert?

also wie ich das safe programmiere mit den buffern für die 
ethernet-pakete weiß ich noch nicht so genau...

mit sizeof (struct data) bekomm ich den speicherplatz raus, der von 
einem Buffer benötigt wird, die Startadresse vom Buffer habe ich, aber 
wie ich diesen Speicher dann wirklich absichern kann - wahrscheinlich 
macht das dann wirklich alles der Compiler und ich mach mir vllt zu 
viele gedanken...

Bernd

von Uhu U. (uhu)


Lesenswert?

Bernd Schuster wrote:
> mit sizeof (struct data) bekomm ich den speicherplatz raus, der von
> einem Buffer benötigt wird, die Startadresse vom Buffer habe ich, aber
> wie ich diesen Speicher dann wirklich absichern kann - wahrscheinlich
> macht das dann wirklich alles der Compiler und ich mach mir vllt zu
> viele gedanken...

Nein, der Compiler macht in dem Fall garnichts. Du darfst beliebigen 
Unsinn schreiben, solange der syntaktisch korrekt ist, regt der Compiler 
sich nicht auf.

Es ist also Aufgabe des C-Programmierers, dafür zu sorgen, daß 
physikalische Pufferlängen nicht überschritten werden.

Nun kann es aber sein, daß ein großer Puffer nur wenig 'echte' Daten 
enthält. Man löst das Problem dadurch, daß im Programm ständig aktuelle 
Angaben darüber geführt werden, wo das logische Ende der Daten ist.

Paradebeispiel sind Strings:
1
   char Puffer[1024] = "Hallo";
Dieses Codeschnipsel legt einen Puffer mit 1024 char an - das ist die 
physikalische Größe.

Darin liegt der Text "Hallo", bestehend aus den 5 Buchstaben, die du 
siehst und ein abschließendes \0 - Character.
1
   unsigned physSize = sizeof Puffer;     // 1024
2
   unsigned logSize  = strlen(Puffer);    // 5 - \0 wird nicht mitgezählt.

von Bernd S. (mms)


Lesenswert?

das weiß ich, aber wenn ich eine feste startadresse habe im speicher, 
die den Anfang des Buffers kennzeichnet, sowie dessen Größe ich ja auch 
weiß und somit auch entsprechend reservieren könnte, aber wie sage ich 
(code) dass dieser Speicher genau ab dieser Adresse für den Buffer 
belegt werden soll.

Also diesen Buffer auf eine festgelegte Adressbereich mappen kann

Bernd

von Uhu U. (uhu)


Lesenswert?

Du belegst garnichts - du weißt, wo der Puffer beginnt und wie lang er 
ist. Das mußt du beachten, wenn du die Programmteile schreibst, die mit 
dem Puffer arbeiten.

malloc ist dafür auf jeden Fall nicht geeignet, weil es sich um einen 
Hardwarepuffer mit fester Adresse und Länge handelt und der deswegen 
nicht Teil des Heap sein darf. (Im Heap dürfen nur allgemeinverwendbare 
Speicherbereiche liegen, weil der Heapmanager (malloc ist ein Teil 
davon) selbst entscheidet, mit welchem Speicherbereich eine Anfrage 
erfüllt wird.)

Lies das nochmal: Beitrag "Re: struct: mehrere variablen erstellen"

von Bernd S. (mms)


Lesenswert?

>Du belegst garnichts - du weißt, wo der Puffer beginnt und wie lang er
>ist. Das mußt du beachten, wenn du die Programmteile schreibst, die mit
>dem Puffer arbeiten.

ok - d.h. so wie das atmel programmiert hat funktioniert das auf jeden 
Fall safe. Nur muss ich als Programmierer wissen wo dieser Buffer im 
Speicher liegt und dann entsprechend mit den Zeigern auf diesen Buffer 
zugreifen um die relevanten Informationen zu erhalten, aber das wars 
dann auch.

>malloc ist dafür auf jeden Fall nicht geeignet, weil es sich um einen
>Hardwarepuffer mit fester Adresse

d.h. eigentlich benötigt man malloc gar nicht - bzw. nur für Buffer, die 
von der Hardware schon bereitgestellt werden, um z.B. darin die 8Bits 
der seriellen Schnittstelle zu speichern... mich als Programmierer 
betrifft aber Malloc gar nicht, weil der Hardwarebuffer schon vorgegeben 
und reserviert ist, oder?

Und wenn ich mit struct eine variable anlege, wird der speicher dafür 
gleich mit reserviert - und in diesem Fall ist es mir als Programmierer 
egal wo der Speicherplatz dafür reserviert wird; wird alles für mich 
erledigt.

Bernd

von Uhu U. (uhu)


Lesenswert?

> d.h. eigentlich benötigt man malloc gar nicht - bzw. nur für Buffer, die
> von der Hardware schon bereitgestellt werden, um z.B. darin die 8Bits
> der seriellen Schnittstelle zu speichern... mich als Programmierer
> betrifft aber Malloc gar nicht, weil der Hardwarebuffer schon vorgegeben
> und reserviert ist, oder?

Nein. Die Hardwarepuffer müssen von der Verwaltung durch den 
Heapmanager ausgeschlossen sein. Sie sind dem Heapmanger unbekannt und 
er hat damit nichts zu tun.

Auf Windows-PCs gibt es das auch: Der Adressraum einer Anwendung ist 
maximal 3 GB, während ein 32-Bit-Rechner 4 GB 'kann'. Im obersten GB 
'liegen' u.a. die Hardware-Puffer für PCI-Geräte, das BIOS-Rom u.ä. 
Stichwort: Memory mapped I/O.

'liegen' deshalb, weil diese Speicherbereiche von der MMU (Memory 
Managment Unit) dort hinein gemapt werden. Auf einem anderen OS können 
sie irgendwo anders liegen

von Bernd S. (mms)


Lesenswert?

vielen dank für die informationen... das mit den Hardwarepuffern hab ich 
jetzt verstanden.

Der Heap und Stack befindet sich immer auf dem internen RAM - oder kann 
dieser sich auch auf einem externen SDRAM befinden? Im Startup-Skript 
definiere ich ja nur die Größe des Heaps und des Stacks aber nicht seine 
Position. (wird dann wahrscheinlich im linker-skript festgelegt).

1
#define AT91C_EMAC_TDLIST_BASE  0x20010000
2
   AT91PS_TdDescriptor tdList = (AT91PS_TdDescriptor)AT91C_EMAC_TDLIST_BASE;

Und das hier funktioniert deshalb, weil ich im externen SDRAM selbst 
bestimme wo welche Daten gespeichert werden sollen - und nicht das O/S 
etc.

Ich glaub ich muss mir mal die genaue Definition von Heapmanager und die 
Speicherverwaltung anschauen - kennst du dazu hilfreiche suchbegriffe 
(bei denen erklärt wird, wer sich wann über die Aufteilung des Speichers 
und dessen Inhalt Gedanken machen muss)?

Bernd

von Uhu U. (uhu)


Lesenswert?

Hier findest du einige sehr nützliche Bücher, die sich mit dem Thema 
befassen: http://www.mikrocontroller.net/buecher/

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.