Forum: Compiler & IDEs struct als Funktionsrückgabe


von Florian H. (viper2000)


Lesenswert?

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
struct TESTSTRUCT
2
{
3
  bool success;
4
  uint8_t adcValues[6];
5
  uint16_t threshold;
6
};

adc.c:
1
struct TESTSTRUCT getValues(void)
2
{
3
  struct TESTSTRUCT tempstruct;
4
  /* hier wird die Struct gefüllt */
5
  return tempstruct;
6
}

main.c:
1
struct TESTSTRUCT values;
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!

von Simon (Gast)


Lesenswert?

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

von Florian H. (viper2000)


Lesenswert?

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 :)

von der mechatroniker (Gast)


Lesenswert?

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.

von Florian H. (viper2000)


Lesenswert?

@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!

von Florian H. (viper2000)


Lesenswert?

Nochmal ne Frage!

Wo liegt der Unterschied zwischen:
1
typedef union
2
{
3
  uint16_t i16;
4
  struct 
5
  {
6
      uint8_t lsb;
7
      uint8_t msb;                
8
  };
9
} qtcrc_t;
10
11
typedef struct
12
{
13
  bool success;
14
  qtcrc_t crc;
15
} qteepromcrcreturn_t;

und:
1
typedef union QTCRC_U
2
{
3
  uint16_t i16;
4
  struct 
5
  {
6
      uint8_t lsb;
7
      uint8_t msb;                
8
  };
9
} qtcrc_t;
10
11
typedef struct QTEEPROMCRC_S
12
{
13
  bool success;
14
  qtcrc_t crc;
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ß!

von 12er Dude (Gast)


Lesenswert?

Hallo Florian,
1
typedef union
2
{
3
  uint16_t i16;
4
  struct 
5
  {
6
      uint8_t lsb;
7
      uint8_t msb;                
8
  };
9
} qtcrc_t;
10
11
typedef struct
12
{
13
  bool success;
14
  qtcrc_t crc;
15
} qteepromcrcreturn_t;
was machst Du, wenn an anderer Stelle weitere Variablen vom gleichen 
struct Type benötigt werden?
 Aha, ...

Tschü Dude

von Florian H. (viper2000)


Lesenswert?

Ich würde folgendes tun:
1
qteepromcrcreturn_t structNummer1;
2
qteepromcrcreturn_t structNummer2;
3
qteepromcrcreturn_t structNummer3;

Sorry,
blicke es noch nicht so ganz...

von 12er Dude (Gast)


Lesenswert?

Hallo Florian,

da lass mal den Compiler drüber laufen.
1
typedef struct QTEEPROMCRC_S
2
{
3
  bool success;
4
  qtcrc_t crc;
5
} qteepromcrcreturn_t;

qteepromcrcreturn_t ist eine Variable vom Type struct QTEEPROMCRC_S.
1
struct QTEEPROMCRC_S structNummer1;

Tschü Dude

von Simon (Gast)


Lesenswert?

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

von Florian H. (viper2000)


Lesenswert?

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

von 12er Dude (Gast)


Lesenswert?

Hallo Simon,

> Ob das für irgendwelche Zwecke sinnvoll ist? Keine Ahnung.

Funktionsparameter?

Tschü Dude

von Florian H. (viper2000)


Lesenswert?

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:
1
bool qt_Eeprom_CRC(qteepromcrcreturn_t *temp)

von 12er Dude (Gast)


Lesenswert?

Hallo Florian,

ich kann mir nicht vorstellen, dass Dein Compiler dies "unkommentiert" 
übersetzt:
1
typedef struct
2
{
3
  bool success;
4
  qtcrc_t crc;
5
} qteepromcrcreturn_t;
6
7
qteepromcrcreturn_t structNummer1;
8
qteepromcrcreturn_t structNummer2;
9
qteepromcrcreturn_t structNummer3;

Schau mal hier nach, oder such nach einem C Tutorium:
http://de.wikibooks.org/wiki/C-Programmierung:_Komplexe_Datentypen

Tschü Dude

von Simon (Gast)


Lesenswert?

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

von Florian H. (viper2000)


Lesenswert?

12er Dude schrieb:
> Hallo Florian,
>
> ich kann mir nicht vorstellen, dass Dein Compiler dies "unkommentiert"
> übersetzt:
>
1
> typedef struct
2
> {
3
>   bool success;
4
>   qtcrc_t crc;
5
> } qteepromcrcreturn_t;
6
> 
7
> qteepromcrcreturn_t structNummer1;
8
> qteepromcrcreturn_t structNummer2;
9
> qteepromcrcreturn_t structNummer3;
10
>

Mein Win-AVR (GCC) übersetzt es ohne Fehler oder Warnungen...

von 12er Dude (Gast)


Lesenswert?

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

von Florian H. (viper2000)


Lesenswert?

Okay, danke!
Dann wäre das ja geklärt und ich bin nun schlauer :)
Gute Nacht!

von Klaus W. (mfgkw)


Lesenswert?

Florian H. schrieb:
> Nochmal ne Frage!
>
> Wo liegt der Unterschied zwischen:
>
1
> ...
2
> typedef struct
3
> {
4
>   bool success;
5
>   qtcrc_t crc;
6
> } qteepromcrcreturn_t;
7
>
>
> und:
>
>
1
> ...
2
> typedef struct QTEEPROMCRC_S
3
> {
4
>   bool success;
5
>   qtcrc_t crc;
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
   typedef struct meine_liste
2
   {
3
       char name[20];            // irgendwelche Nutzdaten
4
       struct meine_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
   typedef struct meine_liste
2
   {
3
       char name[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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Florian H. (viper2000)


Lesenswert?

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!

von Simon K. (simon) Benutzerseite


Lesenswert?

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
typedef struct DieserNameIstFrueherNoetigGewesen
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).

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.