Hallo,
ich nutze eine struct mit verschiedensten Datentypen als
Funktionsrückgabe. Momentan wird die Struct in der Datei adc.c mit adc
Werten und sonstigem gefüllt und an die Mainloop innerhalb der Main
Methode meines Programms zurückgegeben.
Codeschnipsel:
adc.h:
1
structTESTSTRUCT
2
{
3
boolsuccess;
4
uint8_tadcValues[6];
5
uint16_tthreshold;
6
};
adc.c:
1
structTESTSTRUCTgetValues(void)
2
{
3
structTESTSTRUCTtempstruct;
4
/* hier wird die Struct gefüllt */
5
returntempstruct;
6
}
main.c:
1
structTESTSTRUCTvalues;
2
values=getValues();
Das funktioniert auch alles ganz prima. Allerdings habe ich gelesen dass
eine Übergabe per Pointer sinnvoller bzw. performanter sein soll da
nicht jedesmal ein byteweiser Kopiervorgang vom AVR Kern durchgeführt
werden muss. Allerdings habe ich da noch Verständnisprobleme. Wielange
existiert denn die Struktur wenn ich nur einen Pointer auf diese
zurückgebe und wie gebe ich dann den Speicher frei?
Des Weiteren lese ich hier immer wieder was vom Schlüsselwort "typedef
struct". Das habe ich garnicht genutzt und es funktioniert trotzdem.
Vielleicht kann ja auch da jemand was zu sagen.
Viele Grüße!
Willkommen in der Welt von C. Ja, so wie Du es verwendest, müssen Daten
kopiert werden.
Es gibt verschiedene Möglichkeiten, wie man das regeln kann und man muss
aufpassen wie ein Schießhund, damit man sich nicht Speicherkorruption
einfängt.
Vielleicht erstmal ein Beispiel, was nicht geht:
struct TESTSTRUCT *getValues(void)
{
struct TESTSTRUCT tempstruct;
/* hier wird die Struct gefüllt */
tempstruct.success = 1;
return &tempstruct;
}
Hier wird ein Pointer (eine Referenz) auf die temporäre Variable
zurückgegeben, die aber beim Verlassen der Funktion weggeräumt wird. Das
funktioniert bestenfalls per Zufall.
Um korrekt zu arbeiten, kann man getValues den Speicher allokieren
lassen:
struct TESTSTRUCT *getValues(void)
{
struct TESTSTRUCT *tempstruct = malloc (sizeof (struct TESTSTRUCT));
/* hier wird die Struct gefüllt */
tempstruct->success = 1;
return tempstruct;
}
Hier muss man dann darauf achten, dass die zurückgegebene Struktur nicht
von alleine freigegeben wird. Der Compiler kann nicht erkennen, wann die
nicht mehr gebraucht wird. Der aufrufende Code sieht also etwa so aus:
struct TESTSTRUCT *values;
values = getValues();
/* Auswerten der values */
free (values);
Natürlich wird hier wieder und wieder Speicher allokiert und
freigegeben. Das erzeugt ebenfalls Overhead und ergibt halt die Gefahr
von Speicherlecks.
Wenn man weiß, dass man mit einer einzelnen Instanz des tempstruct
hinkommt, dann ist es - zumindest wenn man keine großen Programme vor
sich hat und man den Speicherbedarf überblicken kann - eine ganz gute
Idee, die Struktur vorher anzulegen und "zum Ausfüllen" in die Funktion
reinzureichen:
bool getValues (struct TESTSTRUCT *tempstruct)
{
/* hier wird die Struct gefüllt */
tempstruct->success = 1;
return tempstruct->success;
}
Hier verwenden wir den Rückgabewert für einen Erfolgsstatus, da kann man
ggf. auch drauf verzichten. Der Aufruf sieht dann z.B. so aus:
struct TESTSTRUCT values;
if (getValues(&values))
/* Auswerten der values */
Alternativ:
struct TESTSTRUCT *values = malloc (sizeof (struct TESTSTRUCT));
if (getValues(values))
/* Auswerten der values */
free (values);
Das sind nur einige von diversen Möglichkeiten. Wenn man z.B. weiß, dass
man mit einem globalen Struct hinkommt, kann man ggf. sogar auf die
Parameterübergabe verzichten und einfach die Verwendung der globalen
Variable reinprogrammieren, das würde ich dann tun, wenn ich weiß, dass
ich mit dem vorhandenen RAM auskomme und mit einer Instanz des
TESTSTRUCTs auskomme.
Das mit dem Typedef macht meistens alles ein bischen lesbarer, weil man
dann nicht mehr das struct-Schlüsselwort mit rumschleppen muss.
Die Beispiele sind alle gerade von Hand hingetippt und nicht
ausprobiert, also mit etwas Vorsicht genießen... :-)
Viele Grüße,
Simon
Danke schonmal soweit,
das Schlüsselwort typedef hab ich jetzt verstanden!
Die Sache mit den structs muss ich alledings erstmal verdauen. Ich
glaube ich gehe erstmal schlafen und lasse es morgen Abend mal in Ruhe
auf mich einwirken :)
Weitere Variante:
C++-Compiler nehmen und einen std::auto_ptr<TESTSTRUCT> returnen. Wenig
Kopieraufwand und du hast das Speichermanagement selbst für Sonderfälle
(Allokation geht schief, Exception in der aufgerufenen Methode,...)
erschlagen.
@Simon:
Habe mich für die Variante entschieden die struct in der main.c einmalig
zu Instanzieren und diese als Pointer-Referenz an meine Methode zu
übergeben. Bei jeder Loop wird der Inhalt der Struct mit neuen Werten
überschrieben. Klappt wunderbar!
Danke nochmal für die Hilfe!
Nochmal ne Frage!
Wo liegt der Unterschied zwischen:
1
typedefunion
2
{
3
uint16_ti16;
4
struct
5
{
6
uint8_tlsb;
7
uint8_tmsb;
8
};
9
}qtcrc_t;
10
11
typedefstruct
12
{
13
boolsuccess;
14
qtcrc_tcrc;
15
}qteepromcrcreturn_t;
und:
1
typedefunionQTCRC_U
2
{
3
uint16_ti16;
4
struct
5
{
6
uint8_tlsb;
7
uint8_tmsb;
8
};
9
}qtcrc_t;
10
11
typedefstructQTEEPROMCRC_S
12
{
13
boolsuccess;
14
qtcrc_tcrc;
15
}qteepromcrcreturn_t;
Also einmal mit diesen groß geschriebenen Bezeichnern hinter dem
"struct".
Beides funktioniert im Programm und ich sehe keinen Unterschied in der
Funktionsweise bei der benutzung der Structs...
Gruß!
Naja, mit diesem optionalen Bezeichner vergibst Du einen Namen für den
Struct-Typen. Wenn Du also später aus irgendwelchen Gründen nicht über
den Typedef-Namen zugreifen möchtest, sondern über "struct sowienoch",
dann kannst Du das nur, wenn Du da den Namen vergibst.
Ob das für irgendwelche Zwecke sinnvoll ist? Keine Ahnung.
Viele Grüße,
Simon
12er Dude schrieb:
> Hallo Florian,>> da lass mal den Compiler drüber laufen.>
Ich hab den Compiler drüberlaufen lassen. Er akzeptiert es
anstandslos...ich habe ja den Typ qteepromcrcreturn_t mit typedef
definiert. Dann kann ich ihn ja auch benutzen um verschiedene Variablen
davon zu erzeugen oder nciht!?
Ich verstehe immernochnicht den Sinn von QTEEPROMCRC_S
12er Dude schrieb:
> Hallo Simon,>>> Ob das für irgendwelche Zwecke sinnvoll ist? Keine Ahnung.>> Funktionsparameter?>> Tschü Dude
Als Funktionsparameter kann ich doch auch den neuen typ verwenden...
Das hier funktionier jedenfalls in beiden struct-Fällen:
Hallo Dude.
> > Ob das für irgendwelche Zwecke sinnvoll ist? Keine Ahnung.> Funktionsparameter?
Sagen wir so: Ich kenne keinen wirklichen (also, abgesehen von
Coding-Style-Gewohnheiten) Grund, "struct sowienoch" zu verwenden, wenn
man dafür auch noch einen typedef SowieNoch hat. Auch für
Funktionsparameter würde ich jederzeit das Typedef verwenden wollen,
einfach weil es IMHO um Klassen besser lesbar ist.
Die einzige gute Verwendung für die Struct-Namen sehe ich darin, die
Struct-Definition und das Typedef in zwei Statements
auseinanderzuziehen, damit das nicht gar so verwurschtelt aussieht wie
in den obigen Beispielen:
typedef struct sowienoch SowieNoch;
struct sowienoch { ... };
ist IMHO schöner als:
typedef struct sowienoch { ... } SowieNoch;
aber das ist sicherlich Geschmackssache.
Viele Grüße,
Simon
Sorry Florian, sorry Simon,
ist wohl schon zu spät für mich. Ne, ne, das geht natürlich so, wie es
ursprünglich beschrieben wurde. Alles zurück und "Gute Nacht".
Tschü Dude
Florian H. schrieb:
> Nochmal ne Frage!>> Wo liegt der Unterschied zwischen:>
1
>...
2
>typedefstruct
3
>{
4
>boolsuccess;
5
>qtcrc_tcrc;
6
>}qteepromcrcreturn_t;
7
>
>> und:>>
1
>...
2
>typedefstructQTEEPROMCRC_S
3
>{
4
>boolsuccess;
5
>qtcrc_tcrc;
6
>}qteepromcrcreturn_t;
7
>
>> Also einmal mit diesen groß geschriebenen Bezeichnern hinter dem> "struct".> Beides funktioniert im Programm und ich sehe keinen Unterschied in der> Funktionsweise bei der benutzung der Structs...>> Gruß!
In diesem Fall ist es egal.
Durch das QTEEPROMCRC_S hättest du in der zweiten Variante
die Wahl, als Typ entweder das qteepromcrcreturn_t zu verwenden
oder struct QTEEPROMCRC_S.
Durch das typedef hat man ja für struct QTEEPROMCRC_S einfach
nur einen weiteren Namen geschaffen.
Einen Unterschied macht es dann, wenn man den Typ bereits
innerhalb der struct benötigt.
Daß es eine struct QTEEPROMCRC_S gibt weiß der Compiler nämlich
bereits ab dem struct QTEEPROMCRC_S.
Dagegen kennt er qteepromcrcreturn_t erst ab dem Ende des
typedef und kann diesen Namen deshalb nicht innerhalb der
struct akzeptieren.
Beispiel: Ich möchte eine Liste bauen.
Jedes Listenelement besteht aus irgendwelchen Nutzdaten sowie
einem Zeiger auf das nächste Listenelement. Die Liste selbst
ist dann ein Zeiger auf das erste Element (oder NULL, falls
die Liste leer ist).
Das könnte in C etwa so aussehen:
1
typedefstructmeine_liste
2
{
3
charname[20];// irgendwelche Nutzdaten
4
structmeine_liste*next;// Zeiger auf Nachfolger oder NULL
5
}meine_liste_t;
6
7
...
8
meine_liste_t*die_tolle_liste=NULL;// anfangs leere Liste
Innerhalb der struct kann ich meine_liste_t nicht verwenden,
um next zu definieren, weil es an dieser Stelle nicht bekannt ist.
Für die_tolle_liste dagegen kann es benutzt werden.
Das hier geht also nicht:
1
typedefstructmeine_liste
2
{
3
charname[20];// irgendwelche Nutzdaten
4
meine_liste_t*next;// Zeiger auf Nachfolger oder NULL
5
// ^^ hier Fehler, da meine_liste_t nicht bekannt!
6
}meine_liste_t;
7
8
...
9
meine_liste_t*die_tolle_liste=NULL;// anfangs leere Liste
Da du aber in deiner struct keinen Bezug auf sie selbst hast,
betrifft es dich nicht.
Und eines noch.
Es ist zwar nur eine Konvention, aber es ist eine der wenigen
Konventionen, an die sich fast alle C-Programmierer weltweit halten:
Bezeichner in Grossbuchstaben sind ein Hinweis darauf, dass es sich bei
dem Bezeichner um ein Makro handelt. Dies ist beim Lesen eines
Quelltextes nützlich, weil man erkennen kann, ob da jetzt zb. ein Makro
benutzt wird, oder ob es sich um einen echten Funktionsaufruf handelt.
Bitte halte dich daran, und wirf diese Konvention nicht ohne Not über
den Haufen. Von daher geht
struct QTEEPROMCRC_S
gar nicht.
Oha, die Angewohnheit den Bezeichner von ner struct groß zu schreiben
hab ich aus dem Buch "Embedded C Programming and the Atmel AVR" von
Barnett, Coc, & O´Cull...
Aber nochmal Danke für die Zusätzlichen Informationen. Nun ist alles
klar!
Die Sache mit dem struct, was "zwei Namen" hat ist geschichtlich. Damals
durfte man keine anonymen Structs/Unions erstellen (Bzw. heute
unterstützen so gut wie alle Compiler anynome Structs). Das heißt, man
musste erst die struct anlegen (und dieser einen Namen geben) und danach
das typedef auf diese struct (mit Namen) anlegen.
Oder halt beides in einem Befehl, mit
1
typedefstructDieserNameIstFrueherNoetigGewesen
2
{
3
...
4
}DieserNameIstDerNeueDatentyp;
Im übrigen, ich halte es nicht für unwahrscheinlich, dass der Compiler
bei eingeschalteter Optimierung ein struct-return umfunktioniert als
Call by Reference. Bin mir aber nicht sicher.
Ansonsten sei gesagt, dass auch von großen APIs sowas per Call by
Reference gelöst wird (zB Windows API).