Forum: PC-Programmierung Struct Array an Funktion übergeben


von Peter P. (Gast)


Lesenswert?

Hallo,

ich möchte eine Referenz auf ein Struct Array in einer Funktion einer 
anderen Funktion übergeben.
1
struct STRUCTNAME { .. };
2
struct STRUCTNAME arr[8];
3
4
int function1 (struct STRUCTNAME** sa) { ... };
5
6
int function2 () {
7
  function1 (&arr);
8
};
9
10
...expected 'struct STRUCTNAME **' but argument is of type 'struct STRUCTNAME (*)[8]'

Wie bekomme ich das hin ?

von Nop (Gast)


Lesenswert?

Peter P. schrieb:

> Wie bekomme ich das hin ?

Versuch mal:
1
struct STRUCTNAME { .. };
2
struct STRUCTNAME arr[8];
3
int function1 (struct STRUCTNAME* sa, size_t len) { ... };
4
int function2 () {
5
  function1 (arr, sizeof(arr)/sizeof(arr[0]));
6
};

So oder so verflacht Dein Array bei der Übergabe zu einem Pointer, d.h. 
Du hast keine Längeninformation. Die muß als zweiter Parameter übergeben 
werden. Bei dem sizeof zur Längenbestimmung muß die Definition des 
Arrays im sichtbaren Scope sein, was hier der Fall ist, weil das Array 
global ist.

von PittyJ (Gast)


Lesenswert?

Zuerst mache ich immer eine typedef

typedef struct
{
int         Temperature;
double      Position;
} Combityp;

Dann kann ich danach mit dem Combityp arbeiten. Das macht die Sache 
übersichtlicher.

Combityp array[17];

int function1 (Combityp* sa) { ... };

Und die Doppel-Sterne sind da überflüssig.

von Peter P. (Gast)


Lesenswert?

OK, vielen Dank.
Das funktioniert schonmal als "workaround" und der Code läuft, aber 
beantwortet nicht die eigentliche Frage.

von Oliver S. (oliverso)


Lesenswert?

Peter P. schrieb:
> aber
> beantwortet nicht die eigentliche Frage.

Die hat Nop doch schon beantwortet.

Schreib dir auf, was STRUCTNAME für ein typ ist, was arr für ein Typ 
ist, und was du als Parameter für function1 übergeben willst. Wenn du es 
nicht hinbekommst, mach das gleiche mit einem int stat dem struct. Dann 
verstehst du es auch.

Der Tipp mit dem typedef ist aber trotzdem bedenkenswert.

Oliver

: Bearbeitet durch User
von Peter P. (Gast)


Lesenswert?

Ja, Du hast recht und typedef ist hier mein Freund.

Thema ist somit gelöst.

Danke!

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wenn du wirklich einen Pointer auf das Struct-Array übergeben möchtest,
geht das so:

1
int function1 (struct STRUCTNAME (*sa)[8]) { ... }
2
3
int function2 () {
4
  function1 (&arr);
5
  ...
6
}

Das ist aber eher unüblich. Außerdem ist das übergebene Array auf exakt
8 Elemente beschränkt (was ein Vorteil oder Nachteil sein kann)

von Peter P. (Gast)


Lesenswert?

Perfekt, genau das wollte ich wissen.
Wobei mir der "workaround' mit typedef am Besten gefällt und wohl auch 
üblicher ist.

schönen Dank an Alle

von A. S. (Gast)


Lesenswert?

Peter P. schrieb:
> ich möchte eine Referenz auf ein Struct Array in einer Funktion einer
> anderen Funktion übergeben.

Dein Problem ist ein anderes:

&arr gibt es nicht!

arr (oder &arr{0]) ist das, was Du meinst. Ein Ptr auf eine liste (hier 
8, aber egal) von Strukturen.

Deine Funktion erwartet den Ptr auf einen Ptr. Entweder also
1
int f1 (struct STRUCTNAME* sa) { ... }; // nur ein * statt **
oder extra einen Ptr anlegen, der auf arr zeigt und damit aufrufen
1
struct STRUCTNAME *p=arr;
2
3
  function1(&p);

Wenn Du wirklich 8 Ptr haben wolltest (also 8 Ptr nacheinander in einer 
Liste, dann hätte es
1
struct STRUCTNAME *arr[8];
geheißen, ... und funktioniert.

von Yalu X. (yalu) (Moderator)


Lesenswert?

A. S. schrieb:
> &arr gibt es nicht!

Doch, gibt es schon. Das ist ein Pointer auf ein Array.

> arr (oder &arr{0]) ist das, was Du meinst. Ein Ptr auf eine liste (hier
> 8, aber egal) von Strukturen.

Das ist ein Pointer auf das erste Element des Arrays.

von A. S. (Gast)


Lesenswert?

Yalu X. schrieb:
> Doch, gibt es schon. Das ist ein Pointer auf ein Array.

in diesem Kontext

Peter P. schrieb:
> struct STRUCTNAME arr[8];
> int function2 () {
>   function1 (&arr);

sind arr und &arr für den Compiler (*) idntisch.

Bei anderen Definition von arr ist es ggf. anders.

(* Wenn er es erlaubt. Ob der Compiler das erlauben muss, weiß ich 
nicht. Bisher taten sie es bei mir mit entsprechender Warnung)

von Yalu X. (yalu) (Moderator)


Lesenswert?

A. S. schrieb:
> in diesem Kontext
> Peter P. schrieb:
>
>> struct STRUCTNAME arr[8];
>> int function2 () {
>> function1 (&arr);
>
> sind arr und &arr für den Compiler (*) idntisch.

Beide sind zwar numerisch gleich (gleiche Speicheradressen), haben aber 
unterschiedliche Datentypen, die nicht implizit ineinander konvertierbar 
sind. Deswegen führt (je nach Deklaration von function1) mindestens 
eines von beiden zu einer Fehlermeldung.

von A. S. (Gast)


Lesenswert?

Yalu X. schrieb:
> Deswegen führt (je nach Deklaration von function1) mindestens
> eines von beiden zu einer Fehlermeldung.

OK. Ich wette eine Kiste Bier dagegen.

Beide (arr und &arr) sind bei Deinem meistgenutzten Compiler und 
gegebener Definition von arr gleichwertig. Wobei &arr natürlich eine 
Warnung wirft, weil es das strenggenommen nicht gibt. Und natürlich 
unterscheiden die sich, wenn z.B. ein Feld angegeben wird, dann ist 
"arr[0]" natürlich etwas anderes als "&arr[0]".

Probier es aus, wenn es einen Unterschied gibt, schicke ich eine Kiste 
rüber.

von Rolf M. (rmagnus)


Lesenswert?

A. S. schrieb:
> Yalu X. schrieb:
>> Deswegen führt (je nach Deklaration von function1) mindestens
>> eines von beiden zu einer Fehlermeldung.
>
> OK. Ich wette eine Kiste Bier dagegen.

Bei mir führt es in C zu einer Warnung und in C++ zu einem Fehler, weil 
der Typ falsch ist, genauso wie z.B.
1
void func(float* f);
2
3
int main()
4
{
5
    int i;
6
    func(&i);
7
}

> Beide (arr und &arr) sind bei Deinem meistgenutzten Compiler und
> gegebener Definition von arr gleichwertig.

Nein.

> Wobei &arr natürlich eine Warnung wirft, weil es das strenggenommen nicht
> gibt.

Natürlich gibt es das. Es ist die Adresse des Arrays.

> Und natürlich unterscheiden die sich, wenn z.B. ein Feld angegeben wird,
> dann ist "arr[0]" natürlich etwas anderes als "&arr[0]".

Damit sagst du ja schon selber, dass arr und &arr eben nicht 
gleichwertig sind. Und das liegt genau daran, dass der Typ 
unterschiedlich ist.

von A. S. (Gast)


Lesenswert?

Rolf M. schrieb:
> Nein.

Sorry, ich bezog mich nur auf C. Bei C++ magst Du Recht haben. Bei C 
schicke ich Dir auch gerne eine Kiste wenn ich unrecht habe.

von Rolf M. (rmagnus)


Lesenswert?

A. S. schrieb:
> Rolf M. schrieb:
>> Nein.
>
> Sorry, ich bezog mich nur auf C. Bei C++ magst Du Recht haben. Bei C
> schicke ich Dir auch gerne eine Kiste wenn ich unrecht habe.

Der Unterschied zwischen C und C++ ist hier nur, dass es beim einen nur 
eine Warnung, beim anderen eine Fehlermeldung gibt. Ansonsten ist das 
Problem das selbe.

von A. S. (Gast)


Lesenswert?

Rolf M. schrieb:
> Ansonsten ist das Problem das selbe.

Probier es aus. Dein Compiler wird es trotzdem "richtig" machen, also 
gleich. Wenn nicht, sag wo ich das 🍺 hinschicken soll.

von Holger (Gast)


Lesenswert?

Rolf M. schrieb:
>> Wobei &arr natürlich eine Warnung wirft, weil es das strenggenommen nicht
>> gibt.
>
> Natürlich gibt es das. Es ist die Adresse des Arrays.

Das wird von den meisten Compilern so umgesetzt, der Grund hat sich mir 
aber noch nicht erschlossen.
1
myStruct_t arr_1[4];
2
myStruct_t arr_2[4];
3
myStruct_t* poi = arr_1;

Pointer:
Beim Pointer hast du zwei Adressen, einmal die Adresse auf die der 
Pointer zeigt und einmal die Adresse des Pointers selbst (also die 
Speicherstelle an der der Pointer liegt).

Array:
Bei einem Array hast du das nicht, denn "arr_1" ist lediglich ein 
Identifier der für einen Speicherbereich steht. Du kannst zwar auf 
diesen Speicherbereich lesend/schreibend zugreifen aber du kannst dem 
Identifier keinen neuen Wert zuweisen:
1
arr_1 = arr_2; // Compilerfehler

Zumindest beim gcc führen "arr_1" und "&arr_1" jeweils zur gleichen 
Ausgabe, also der Rückgabe der Adresse an der das Array liegt. Den Sinn 
von "&arr_1" habe ich aber ehrlich gesagt nicht so ganz verstanden. 
Meiner Meinung nach ist "arr_1" lediglich ein Identifier der auf einen 
Speicherbereich verweist, keine separate Variable (wie beispielsweise 
ein Pointer) die im Speicher liegt und auf deren Adresse man zugreifen 
könnte.
1
#include <stdio.h>
2
3
typedef struct {
4
        int i1;
5
        int i2;
6
} myStruct_t;
7
8
myStruct_t arr_1[4];
9
myStruct_t arr_2[4];
10
myStruct_t* poi = arr_1;
11
12
13
int main(int argc, char* argv[] )
14
{
15
        // arr_1 = arr_2;
16
        printf("arr_1=%08x\n", arr_1);
17
        printf("&arr_1[0]=%08x\n", &arr_1[0]);
18
        printf("&arr_1=%08x\n", &arr_1);
19
20
        printf("poi=%08x\n", poi);
21
        printf("&poi=%08x\n", &poi);
22
23
        return 0;
24
}

Ausgabe:
1
arr_1=564ce040
2
&arr_1[0]=564ce040
3
&arr_1=564ce040
4
5
poi=564ce040
6
&poi=564ce010

Gruß,

Holger

von Yalu X. (yalu) (Moderator)


Lesenswert?

A. S. schrieb:
> Yalu X. schrieb:
>> Deswegen führt (je nach Deklaration von function1) mindestens
>> eines von beiden zu einer Fehlermeldung.
>
> OK. Ich wette eine Kiste Bier dagegen.

Ok, ich habe das vorher nicht ausprobiert. Beim GCC und Clang im C-Modus
kommt mit den Defaultoptionen tatsächlich nur eine Warnung der Art

  "passing argument from incompatible pointer type"

Trotzdem ist die Übergabe eines Pointers mit inkompatiblem Typ ganz klar
ein Fehler (s. ISO 9899:2017, Abschnitte 6.5.2.2 und 6.5.16.1, jeweils
unter "Constraints"), den der Compiler melden muss (5.1.1.3). Ihm ist es
allerdings freigestellt, in welcher Form dies geschieht, weswegen auch
eine Warnung die Norm erfüllt.

Mit der Option -pedantic-errors (die für standardkonforme Programmierung
immer aktiv sein sollte) werden alle Fehler, für die der Standard eine
Meldung vorschreibt, als Error statt nur einer Warning ausgegeben, so
auch in diesem Fall.

Die defaultmäßige Ausgabe einer Warning statt eines Errors beim GCC und
Clang geschieht vermutlich aus Rücksichtnahme auf Legacy-Code. Im
C++-Modus entfällt dieser Grund, weswegen dort immer ein Error
ausgegeben wird.

A. S. schrieb:
> Probier es aus. Dein Compiler wird es trotzdem "richtig" machen

Ja, das tut er deswegen, weil – wie ich oben schon schrieb – mit arr und
&arr der gleiche numerische Wert an die Funktion übergeben wird,
weswegen die Funktion den Unterschied gar nicht mitbekommt.

Der Unterschied zwischen arr und &arr wird aber in folgendem Beispiel
deutlich:
1
#include <stdio.h>
2
3
int main(void) {
4
  int arr[2] = { 111, 222 };
5
  printf("%d\n",   arr [1]);
6
  printf("%d\n", (&arr)[1]);
7
  return 0;
8
}

von Rolf M. (rmagnus)


Lesenswert?

Holger schrieb:
> Das wird von den meisten Compilern so umgesetzt, der Grund hat sich mir
> aber noch nicht erschlossen.

Der Grund ist, dass es im Standard so vorgeschrieben ist.

> myStruct_t arr_1[4];
> myStruct_t arr_2[4];
> myStruct_t* poi = arr_1;
>
> Pointer:
> Beim Pointer hast du zwei Adressen, einmal die Adresse auf die der
> Pointer zeigt und einmal die Adresse des Pointers selbst (also die
> Speicherstelle an der der Pointer liegt).
>
> Array:
> Bei einem Array hast du das nicht,

Doch, hast du auch.

> denn "arr_1" ist lediglich ein Identifier der für einen Speicherbereich
> steht.

Das gilt aber für jede Variable.

> Du kannst zwar auf diesen Speicherbereich lesend/schreibend
> zugreifen aber du kannst dem Identifier keinen neuen Wert zuweisen:

Das liegt daran, dass ein Array ein so genannter "rvalue" ist. Das r 
soll sagen, dass es nur auf der rechten Seite einer Zuweisung erlaubt 
ist.

> arr_1 = arr_2; // Compilerfehler
>
> Zumindest beim gcc führen "arr_1" und "&arr_1" jeweils zur gleichen
> Ausgabe, also der Rückgabe der Adresse an der das Array liegt.

Es ist aber logisch etwas anderes. Das eine ist die Adresse des ersten 
Elements, das andere die Adresse des Arrays selbst. Während beide auf 
die selbe Speicherstelle verweisen, ist ihr Typ unterschiedlich. Wenn du 
sowas hast wie
1
struct X
2
{
3
    int a;
4
    int b;
5
};
6
7
struct X x;
dann verweisen &x und &x.a auch auf die selbe Speicherstelle, sind aber 
verschiedene Dinge. Bei einem Array ist das auch so. Bei
1
int arr[10];
zeigen &arr und &arr[0] auch auf die selbe Speicherstelle, sind aber von 
unterschiedlichem Typ. Nur gibt es dort einen Mechanismus, durch den arr 
fast überall als verkürzte Schreibweise für &arr[0] verwendet werden 
kann.

> Den Sinn von "&arr_1" habe ich aber ehrlich gesagt nicht so ganz
> verstanden.

Das ist das selbe wie bei jedem anderen Typ. Man bekommt einen Zeiger 
auf das entsprechende Objekt.
1
int i;
2
int* p1 = &i; //Adresse des Integers i vom Typ Zeiger auf int
3
4
void f(void);
5
void (*p2)(void) = &f; // Adresse des Funktion f vom Typ Zeiger
6
                       // auf Funktion ohne Parameter und Returnwert
7
8
int a[10];
9
int (*p3)[10] = &a; // Adresse des Arrays a vom Typ Zeiger auf Array
10
                    // aus 10 int. Man kann über diesen Zeiger durch
11
                    // Dereferenzierug auf das Array zugreifen
12
(*p3)[3] = 5;
13
14
int* p4 = &a[0];    // Zeiger auf das erste Element des Arrays vom
15
                    // Typ Zeiger auf int
16
17
int* p5 = a;        // Äquivalent zu p4

> Meiner Meinung nach ist "arr_1" lediglich ein Identifier der auf einen
> Speicherbereich verweist, keine separate Variable (wie beispielsweise
> ein Pointer) die im Speicher liegt und auf deren Adresse man zugreifen
> könnte.

Nein, es ist wie bei jedem anderen Typ auch einfach der Name der 
Variablen. Es gibt lediglich in der Anwendung ein paar Besonderheiten.
1
#include <stdio.h>
2
typedef char my_array[10];
3
my_array arr;
4
5
int main()
6
{
7
    char*     p1 = arr; // An dieser Stelle äquivalent zu &arr[0]
8
    my_array* p2 = &arr;
9
    printf("Größe von arr: %zd\n", sizeof arr);
10
    // bei sizeof ist &arr[0] nicht äquivalent zu arr
11
    printf("Größe von &arr[0]: %zd\n", sizeof &arr[0]);
12
    printf("Größe von dem, worauf p1 zeigt: %zd\n", sizeof *p1);
13
    printf("Größe von dem, worauf p2 zeigt: %zd\n", sizeof *p2);
14
}

Beitrag #6835443 wurde von einem Moderator gelöscht.
von Yalu X. (yalu) (Moderator)


Lesenswert?

@A. S.:

Jetzt hast du mir, während ich eine Antwort auf deinen letzten Beitrag
schrieb, einfach den Boden unter den Füßen weggezogen  :)

von A. S. (Gast)


Lesenswert?

Yalu X. schrieb:
> Der Unterschied zwischen arr und &arr wird aber in folgendem Beispiel
> deutlich:

Ja, vielen Dank. And Dich und Rolf. Ich bin froh, dass ich die Kiste 
Bier nur auf den Effekt und nicht auf die zugrunde liegende, natürlich 
abstruse Erklärung von mir ausgelobt habe ;-)

von W.S. (Gast)


Lesenswert?

PittyJ schrieb:
> Zuerst mache ich immer eine typedef

na klasse! und was versprichst du dir davon?

Gewöhnlichermaßen wird ein Struct so definiert:
1
struct Combityp
2
{
3
int         Temperature;
4
double      Position;
5
};

Du hast ganz vergessen, daß 'typedef' nicht zum Definieren eines Typs 
gedach ist, sondern lediglich zum Erzeugen von sowas wie einem Alias. 
Dehalb sieht dein Beispiel abstrakt etwa so aus:
1
typedef NamenloserStruct NeuerName;

Hierbei ist der namenlose Ausgangs-Struct-Typ im Programm nicht 
benutzbar, weil er zwar definiert ist, aber keinen Namen hat. Von daher 
kann man ihn nur über seinen Alias 'NeuerName' benutzen.

W.S.

von Rolf M. (rmagnus)


Lesenswert?

W.S. schrieb:
> PittyJ schrieb:
>> Zuerst mache ich immer eine typedef
>
> na klasse! und was versprichst du dir davon?

Er verspricht sich davon vermutlich, bei der Nutzung des Typs das 
Wörtchen "struct" nicht davor schreiben zu müssen.

> Hierbei ist der namenlose Ausgangs-Struct-Typ im Programm nicht
> benutzbar, weil er zwar definiert ist, aber keinen Namen hat. Von daher
> kann man ihn nur über seinen Alias 'NeuerName' benutzen.

Und warum ist das ein Problem?
Man könnte statt deinem Beispiel auch das schreiben:
1
typedef struct Combityp
2
{
3
int         Temperature;
4
double      Position;
5
} Combityp;
oder entzerrt
1
struct Combityp
2
{
3
int         Temperature;
4
double      Position;
5
};
6
typedef struct Combityp Combityp;
Wäre das dann besser, auch wenn man es nachher nie unter seinem Namen 
"struct Combityp", sondern nur unter dem Alias "Combityp" anspricht?
Ich nutze es zwar normalerweise auch ohne Typedef, weil ich es dann 
klarer finde, als das "struct" hinter einem Typedef zu verstecken, aber 
da sind die Geschmäcker unterschiedlich. Wenn man aber schon den Typedef 
hat, wozu soll dann ein zusätzlicher nie genutzter Name für die struct 
selbst gut sein?

Ich nutze übrigens manchmal sogar Struct-Typen, die weder einen Namen, 
noch ein Alias haben, um eine einfache Möglichkeit zu haben, Daten in 
einer größeren struct zu gruppieren:
1
struct MyModuleData
2
{
3
    struct
4
    {
5
        int a;
6
        float b;
7
    }
8
    inputs;
9
10
    struct
11
    {
12
        int c;
13
        fload d;
14
    }
15
    outputs;
16
};
17
18
MyModuleData data;
19
// …
20
data.inputs.a = 5;

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

Rolf M. schrieb:
> Und warum ist das ein Problem?
> Man könnte statt deinem Beispiel auch das schreiben:

Ja, das ist erlaubt, aber unschön.

Vor allem Anfänger sind immer verwirrt, wenn derselbe Bezeichner für 
zwei verschieden Dinge benutzt wird.

Das führt dann dazu, dass sich einbrennt: "Ich muss den gleichen Namen 
hier und hier hinschreiben" und die Unterschiede überhaupt nicht mehr 
klar werden.

Die Nutzung um nur ein "struct" zu sparen verhindert m.E. gerade die 
stärken von typedef: Elementare Felder zentral ändern zu können (z.B. 
ein tCount als uint8_t, int oder unsigned long oder ..) oder zentrale 
Typen (mit vielen Klammern und Pointern) zu vereinfachen.

Stattdessen Voodoo damit 2 Strukturen wechselseitig auf sich zeigen 
können. Irgendwas seit Urzeiten kopiertes, vom Gründervater der 
Abteilung ausgeklügeltes mit 6 typedefs, statt einfach
1
struct a;
2
struct b;
3
4
struct a{
5
    struct b *bref;
6
    ...
7
}
8
struct b{
9
    struct a * aref;
10
    ...
11
}

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.