Forum: PC-Programmierung Größe eines externen Arrays in C


von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Ich habe ein Problem mit der Ermittlung der Elemente in einem Array was 
in einer anderen Quelltext Datei deklariert wird:

header.h:
1
typedef struct {
2
 const int a;
3
 const int b;
4
} element_t;
5
6
extern const element_t element[];
source.c:
1
#include "header.h";
2
3
const element_t element[] = { {1,2}, {3,4}, {5,6} };
main.c:
1
#include "header.h";
2
3
int anzahl_elemente = sizeof(element) / sizeof(element_t);

Das dieses nicht hinhauen kann ist mir natürlich klar, da der Compiler 
beim kompilieren nicht wissen kann, wie groß das Array element 
tatsächlich ist, da er nur weiß, dass es ein Pointer auf ein element_t 
ist und sich drauf verlassen soll, dass der Speicherbereich vom Linker 
schon noch dazugelinkt wird.

Jetzt stellt sich für mich nur die Frage wie man es sinnvoll besser 
machen kann. Das Beispiel ist wirklich nur als Beispiel gedacht, in 
Wirklichkeit ist das Strukt komplexer und die source.c variabel und je 
nach Konfiguration anders.

Ursprünglich war der Inhalt der source.c mit in der main.c und da gabs 
natürlich kein Problem, nur die Defines für die verschiedenen 
Konfigurationen wurden immer länger, so dass diese jetzt in seperate 
Header und Source Dateien verteilt ist.

Als erste Idee kam mir dazu in den Sinn einfach ein Struct zu definieren 
mit einem Pointer auf element_t und der Anzahl der Elemente:

header.h:
1
typedef struct {
2
 const int a;
3
 const int b;
4
} element_t;
5
6
typedef struct {
7
 const element_t *element;
8
 const int anzahl;
9
} config_t;
10
11
extern const config_t config;
source.c:
1
#include "header.h";
2
3
const element_t element[] = { {1,2}, {3,4}, {5,6} };
4
5
const config_t config = { element, sizeof(element) / sizeof(element_t) };
main.c:
1
#include "header.h";
2
3
int anzahl_elemente = config.anzahl;

Aber ich frage mich ob man das nicht irgendwie eleganter lösen kann...

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Du musst die Größe halt auf irgend eine Art mit übergeben. Eleganter 
geht es in C nicht.

Oliver

von dummschwaetzer (Gast)


Lesenswert?

#define count_elemente 3
const element_t element[count_elemente] = { {1,2}, {3,4}, {5,6} };

von dummschwaetzer (Gast)


Lesenswert?

das
#define count_elemente 3
natürlich in eine Header-Datei oder Global, als compilereinstellung

von Klaus W. (mfgkw)


Lesenswert?

header.h:
1
#ifndef _HEADER_H_
2
#define _HEADER_H_
3
4
5
#include <stddef.h>
6
7
typedef struct
8
{
9
  const int a;
10
  const int b;
11
} element_t;
12
13
extern size_t    nElemente;
14
15
#endif /* ifndef _HEADER_H_ */

source.c:
1
#include "header.h"
2
3
const element_t element[] = { {1,2}, {3,4}, {5,6} };
4
5
size_t    nElemente = sizeof(element)/sizeof(element[0]);

main.c:
1
#include <stdlib.h>
2
#include <stddef.h>
3
#include <stdio.h>
4
5
#include "header.h"
6
7
8
int main( int nargs, char **args )
9
{
10
  printf( "Das Feld hat %lu Elemente\n", (unsigned long)nElemente );
11
12
  return 0;
13
}


Ausgabe:
1
klaus@lap7:~$ gcc -Wall main.c source.c
2
klaus@lap7:~$ ./a.out
3
Das Feld hat 3 Elemente

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Irgendwie ist das die gleiche Lösung wie mit dem Struct nur das es eben 
einfach als globale Variable drin liegt und ich für jede Art von 
Elementen eine eigene extern Deklaration machen muss anstatt diese 
gesammelt in einem (config_t) struct zu machen.

von Klaus W. (mfgkw)


Lesenswert?

Es ist insofern nicht gleich, als es kompiliert und läuft.

Natürlich kannst du nach demselben Strickmuster auch statt einer size_t 
eine struct mit mehreren machen.

PS: in meinem Vorschlag fehlt bei nElemente noch jeweils ein const in 
der .h und der .c.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Klaus W. schrieb:
> Es ist insofern nicht gleich, als es kompiliert und läuft.

Naja, bis auf die ein oder andere Kleinigkeit welche dem freien 
schreibseln hier im Editor geschuldet ist, läuft meine 2. Variante ja 
auch, das war nie die Frage.

von Klaus W. (mfgkw)


Lesenswert?

Deinen Vorschlag mit der struct finde ich für C durchaus angemessen.

Wesentlich eleganter geht es dann wohl erst in C++...

von Tim T. (tim_taylor) Benutzerseite


Angehängte Dateien:

Lesenswert?

Klaus W. schrieb:
> Deinen Vorschlag mit der struct finde ich für C durchaus angemessen.
>
> Wesentlich eleganter geht es dann wohl erst in C++...

Ja, das Struct kommt halt am nächsten an eine Klasse ran. Ich hatte halt 
überlegt ob es mit kreativem Header und/oder Präprozessor Missbrauch 
nicht noch eine Lösung dafür geben könnte.

PS: Hab mal die funktionierende Beispiellösung mit dem Struct angehängt, 
das funktioniert wie gesagt und kompiliert auch mit -Wall, -Wextra und 
-pedantic ohne zu murren.

von Klaus W. (mfgkw)


Lesenswert?

Aus dem typedef für element_t würde ich die const von a und b wegnehmen; 
du machst ja nachher eh das ganze Feld nochmal const. Noch conster geht 
nicht.

Sonst würde ich da nicht mehr viel verpfuschen.
Tricksen kann man noch viel, das macht es aber nicht unbedingt besser 
oder gar verständlicher.

von foobar (Gast)


Lesenswert?

> Ich hatte halt überlegt ob es mit kreativem Header und/oder
> Präprozessor Missbrauch nicht noch eine Lösung dafür geben könnte.

Ich würde das nur weiter treiben, wenn ich die Größe in den anderen 
Dateien als (compile-time-)Konstante bräuchte.  Das bedeutet, die Größe 
steht in einem separatem .h-File, das im Buildprozesses nach dem 
Kompilieren von source.c (denn erst danach ist die tatsächliche Größe ja 
bekannt) automatisiert erstellt/aktualisiert wird.  Verlangt dann ein 
paar Tricksereien im Buildsystem (extra Regeln/Abhängigkeiten, 
extrahieren der Größe aus dem .o-File, etc), die man nur macht, wenn's 
unbedingt nötig ist.

von Wilhelm M. (wimalopaan)


Lesenswert?

In C zerfallen die Array-Bezeichner (fast) immer (wie in diesem Fall) zu 
einem Zeiger (decay of array designator). Das geht nur in der 
Übersetzungseinheit, in dem der Compiler die vollständige Deklaration 
des Array-Bezeichners gesehen hat. Demzufolge geht es eben nicht 
zwischen Übersetzungseinheiten bzw. bei Parameterlisten.

Da hier schon C++ angesprochen wurde: daher kommt auch die Äußerung von 
Bjarne: C-Arrays sind so dummm, sie kennen noch nicht einmal ihre Länge.

Ist sofern ist die Lösung mit dem eigenen Datentyp (struct) schon ok. 
Und bei Parameterlisten auch die Lösung mit einem zusätzlichen Parameter 
für die Länge. Beides allerdings bei statischen Arrays Krücken. Aber so 
ist C nun mal ...
Die Variante mit einem CPP-Macro ist natürlich wie immer die 
schlechteste Variante.

von Le X. (lex_91)


Lesenswert?

Aktuell exponiert deine source.c (bzw. dessen header) nur ein Symbol, 
nämlich extern const element_t element[];

Das reicht wie du festgestellt hast nicht, du musst also ein zweites 
Symbol exponieren, nämlich die Länge von element[].

Jetzt hast du zwei Möglichkeiten:
- per Makro
- per Variable und forward declaration

Die Makro-Lösung hat den Nachteil dass du die Länge hartcodiert angeben 
musst, also
#define len 3
Spielereien mit sizeof() im Makro werden nicht funktionieren da in 
main.c die Arrays ja bereits zerfallen sind.
Also musst du das Makro händisch aktuell halten.

Deswegen würde ich hier eine Variable "element_len" exponieren. Wenn die 
Variable innerhalb von source.c angelegt wird kannst du da auch mit 
sizeof() arbeiten. Im HEader hast du dann nur noch eine forward 
declaration

Ob du eine losgelöste Variable exponierst oder die Infos in einem struct 
sammelst ist dann syntaktischer Zucker, kommt auf deinen Gusto an.

von Wilhelm M. (wimalopaan)


Lesenswert?

Le X. schrieb:

> Die Makro-Lösung hat den Nachteil dass du die Länge hartcodiert angeben
> musst, also
> #define len 3
> Spielereien mit sizeof() im Makro werden nicht funktionieren da in
> main.c die Arrays ja bereits zerfallen sind.
> Also musst du das Makro händisch aktuell halten.

Naja, das ist ja nicht der Nachteil der Macro-Lösung: Du kannst das 
Macro ja auch in der Definition des Arrays verwenden, damit hast Du 
keine Duplikation.

Der Nachteil der CPP-Macros ist, dass sie nicht ge-scoped sind und 
einfach nur Textersatz, können also an beliebigen Stellen "zuschlagen".

> Ob du eine losgelöste Variable exponierst oder die Infos in einem struct
> sammelst ist dann syntaktischer Zucker, kommt auf deinen Gusto an.

Nein, das ist kein syntaktischer Zucker, sondern - im Gegenteil - ein 
ganz wichtiger Vorteil: denn mit einem struct verknüpfst Du 
zusammengehörige Informationen zu einer Entität. Leider ist es in C 
nicht ganz, nur fast ein Datentyp, da man freie Funktionen nicht 
überladen kann und es keine Elementfunktionen gibt.

von Wilhelm M. (wimalopaan)


Lesenswert?

Klaus W. schrieb:
> Aus dem typedef für element_t würde ich die const von a und b wegnehmen;
> du machst ja nachher eh das ganze Feld nochmal const. Noch conster geht
> nicht.

Das eigentliche Problem daran ist, dass die Elemente damit nicht mehr 
zuweisbar sind.

von Wilhelm M. (wimalopaan)


Lesenswert?

Ich würde es in der Tat so machen:
1
typedef struct {
2
  int a;
3
  int b;
4
} element_t;
5
6
typedef struct {
7
  element_t* const element;
8
  const size_t anzahl;
9
} config_t;

Damit

- bleiben Objekte vom Type element_t zuweisbar (das wird wohl in Deinem 
Fall sinnvoll sein),
- der Zieltyp (pointee) bleibt änderbar in config_t,
- der Zeiger selbst (pointer) wie auch die zugehörige Länge sind 
immutable,
- was dazu führt, dass die Invariante nicht nachträglich verletzt werden 
kann,
- der Datentyp für Anzahl ist korrekt (nicht-negativ und nicht zu 
klein).

von A. S. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Das eigentliche Problem daran ist, dass die Elemente damit nicht mehr
> zuweisbar sind

Bei der Definition sind sie bekannt. Das reicht, da können sie 
zugewiesen werden.

von A. S. (Gast)


Lesenswert?

Die wichtigere Frage: wofür brauchst Du die Anzahl?

Und warum ist sie vorher nicht bekannt?

Am Ende ist es recht selten, wo man "nur" die Anzahl braucht. Oft 
braucht man noch weitere Infos im struct oder eine terminierte Liste ist 
ausreichend.

von Wilhelm M. (wimalopaan)


Lesenswert?

A. S. schrieb:
> Die wichtigere Frage: wofür brauchst Du die Anzahl?

Meistens möchte man drüber iterieren.

> Und warum ist sie vorher nicht bekannt?

Doch, das ist sie ja.

> Am Ende ist es recht selten, wo man "nur" die Anzahl braucht. Oft
> braucht man noch weitere Infos im struct oder eine terminierte Liste ist
> ausreichend.

Natürlich kann er auch ein Sentinel verwenden, dazu müsste der TO 
verraten, ob es bei den Elementen des struct einen ungültigen Wert gibt, 
den man als Sentinel verwenden kann.

von Udo K. (udok)


Lesenswert?

So geht es auch:
Btw. Was sollen die "const" in element_t bewirken?
1
// Im Header
2
typedef struct {
3
    int a;
4
    int b;
5
} element_t;
6
7
extern const element_t element[100];
8
9
// Test
10
int main()
11
{
12
    printf("%d\n", sizeof(element)/sizeof(element_t));
13
}

Gruss,

Udo

von Wilhelm M. (wimalopaan)


Lesenswert?

Udo K. schrieb:
> So geht es auch:
> Btw. Was sollen die "const" in element_t bewirken?
>
>
1
> // Im Header
2
> typedef struct {
3
>     int a;
4
>     int b;
5
> } element_t;
6
> 
7
> extern const element_t element[100];
8
> 
9
> // Test
10
> int main()
11
> {
12
>     printf("%d\n", sizeof(element)/sizeof(element_t));
13
> }
14
>

Nicht Dein Ernst jetzt, oder?
Lies doch erstmal hier den Thread, bevor Du die Diskussion noch vor den 
Anfang zurück spulst!

von Udo K. (udok)


Lesenswert?

Tim T. schrieb:
> Das dieses nicht hinhauen kann ist mir natürlich klar, da der Compiler
> beim kompilieren nicht wissen kann, wie groß das Array element
> tatsächlich ist, da er nur weiß, dass es ein Pointer auf ein element_t
> ist und sich drauf verlassen soll, dass der Speicherbereich vom Linker
> schon noch dazugelinkt wird.

Der Code funktioniert doch problemlos?  Der Compiler muss nur die 
Definition
sehen.  Da das Array sowieso const ist, kannst du es auch einfach 
inkludieren,
eventuell ein inline davor setzen.

von Wilhelm M. (wimalopaan)


Lesenswert?

A. S. schrieb:
> Wilhelm M. schrieb:
>> Das eigentliche Problem daran ist, dass die Elemente damit nicht mehr
>> zuweisbar sind
>
> Bei der Definition sind sie bekannt. Das reicht, da können sie
> zugewiesen werden.

Das ist keine Zuweisung, sondern eine Initialisierung.
const-Objekte sind read-only und können nicht zugewiesen werden.

von Gerald K. (geku)


Lesenswert?

In c steht hinter dem Namen immer eine Adresse (Zeiger). Die maximale 
Größe hängt vom adressierbaren Speicherraum des Prozessors ab und 
dadurch letzten Endes vom Compiler.

von Wilhelm M. (wimalopaan)


Lesenswert?

Gerald K. schrieb:
> In c steht hinter dem Namen immer eine Adresse (Zeiger). Die maximale
> Größe hängt vom adressierbaren Speicherraum des Prozessors ab und
> dadurch letzten Endes vom Compiler.

Das ist falsch, wie ich oben schon geschrieben habe: array-Bezeichner 
sind (abstrakte) array-Bezeichner, Zeiger sind Zeiger, und beides 
erstmal unterschiedliche Konzepte. In vielen Kontexten zerfallen 
allerdings array-Bezeichner zu einem Zeiger auf das erste Element.

von Sebastian (Gast)


Lesenswert?

Udo K. schrieb:
> So geht es auch:

Eben. Warum all dieser Umstand die Array-Grösse zu verstecken?

LG, Sebastian

von Mikro 7. (mikro77)


Lesenswert?

Ich programmiere C/++ seit 30 Jahren... und ich mag die Sprache da man 
sehr nah an der Hardware ist. Die (redundanten!) Header haben mich 
allerdings im Laufe der Zeit immer mehr gestört. Und schlimmer, wie hier 
im Thread (initializer lists), manchmal geben sie nicht mal die 
Möglichkeit her, die Definition mit der Deklaration vernünftig zu 
verheiraten.

Der mit C++20 eingeführte export ist mehr als überfällig.

Zurück zum Thema, hier noch eine (eher akademische) Alternative:
1
$ cat foo.h
2
typedef struct {
3
  int a;
4
  int b;
5
} element_t;
6
7
enum {
8
  nelements = 3 // macros are evil
9
};
10
11
extern element_t const (*elements) [nelements];
12
// pointer so the compiler can implicitly type check
13
14
$ cat foo.c
15
#include "foo.h"
16
17
static element_t const array[] = { {1,2}, {3,4}, {5,6} };
18
19
element_t const (*elements)[nelements] = &array;
20
21
$ cat main.c
22
#include "foo.h"
23
#include <stdio.h>
24
25
int main()
26
{
27
  for (int i=0; i<sizeof(*elements)/sizeof(**elements); ++i)
28
    printf("%d %d %d\n", i, (*elements)[i].a, (*elements)[i].b);
29
  return 0;
30
}
31
32
$ gcc -Wall -o main foo.c main.c
33
$ ./main
34
0 1 2
35
1 3 4
36
2 5 6

von kein Problem und bereits gelöst (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Ich würde es in der Tat so machen:
> typedef struct {
>   int a;
>   int b;
> } element_t;
> typedef struct {
>   element_t* const element;
>   const size_t anzahl;
> } c

Dann musst du auch die dazugehörigen Funktionen anlegen, die dir neue 
Elemente von diesem Datentyp anlegen UND gleichzeitig den Counter 
(automatisch) inkrementieren. Dann ist man gleich bei Getter und Setter.

C kennt die Arraygröße. Dort, wo Array angelegt wird, dort soll man auch 
seine Größe ermitteln. Und dort, wo Array bekannt gemacht wird (per 
extern), dort wird auch seine Größe (Größenvariable) (per extern) 
bekannt gemacht. Fertig.

Klaus W. schrieb:
> source.c:#include "header.h"
> const element_t element[] = { {1,2}, {3,4}, {5,6} };
> size_t    nElemente = sizeof(element)/sizeof(element[0]);

von Wilhelm M. (wimalopaan)


Lesenswert?

kein Problem und bereits gelöst schrieb:
> Dann musst du auch die dazugehörigen Funktionen anlegen, die dir neue
> Elemente von diesem Datentyp anlegen UND gleichzeitig den Counter
> (automatisch) inkrementieren. Dann ist man gleich bei Getter und Setter

Da es um statische Arrays geht, ist das Inkrementieren der Anzahl 
sinnlos (und deswegen hier nicht möglich).

kein Problem und bereits gelöst schrieb:
> C kennt die Arraygröße. Dort, wo Array angelegt wird, dort soll man auch
> seine Größe ermitteln.

Das tut er ja auch oben. Alles gut.

> Und dort, wo Array bekannt gemacht wird (per
> extern), dort wird auch seine Größe (Größenvariable) (per extern)
> bekannt gemacht. Fertig.

Nix fertig.
Es geht doch um Kontexte, wo die Definition nicht mehr sichtbar ist und 
der Bezeichner zu einem Zeiger zerfallen ist (Standardbeispiel: 
Parameterlisten).

von fiju (Gast)


Lesenswert?

Die Anzahl der Elemente in die Struc mit aufnehmen

 typedef struct {
    int Anzahl
    int a;
    int b;
 } element_t;

von Wilhelm M. (wimalopaan)


Lesenswert?

fiju schrieb:
> Die Anzahl der Elemente in die Struc mit aufnehmen
>
>  typedef struct {
>     int Anzahl
>     int a;
>     int b;
>  } element_t;

So nach dem Motto, n-fach gemoppelt hält besser ;-)
Schwachsinn! Wie der Name schon sagt, ist element_t der DT eines 
Elementes(!), nicht des Containers!

Beitrag #7154305 wurde von einem Moderator gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

Wullum Iri schrieb im Beitrag #7154305:
> Gehiern

... würde ich mal einschalten

von Mikro 7. (mikro77)


Lesenswert?

Udo K. schrieb:
> So geht es auch:

Das ist der straight-forward Ansatz. Die Elemente in der initializer 
list abzählen und das Array entsprechend deklarieren.

Die implizite (böse) Redundanz ist aber ein Problem: Was wenn man sich 
verzählt hat oder wenn nachträglich die initializer list geändert wird?

Ist die initializer list größer, dann gibt der Compiler bescheid.
Ist die initializer list kleiner, werden implizt Null Elemente 
angehangen. Letzteres möchte man nicht unbedingt.

Beim meinem Ansatz erkennt der Compiler beide Probleme. (Eher 
akademisch, denn wer möchte schont mit einem Pointer auf ein Array 
arbeiten.)

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Bevor das hier weiter ausartet:

In meinem Fall gibt es nicht eine source.c sondern knapp 20, von denen 
aber immer nur genau eine über ein Define vom jeweiligen Compiler 
eingebunden wird. Die knapp 20 Dateien sind für verschiedene 
Mikrocontroller und beinhalten mögliche Konfigurationsvarianten einer 
Software, die sich je nach Controller aber auch noch unterscheiden. Ein 
element_t ist praktisch dabei eine einzelne Konfiguration von denen pro 
Controller mehrere im zur Auswahl stehen, darum das Array von element_t; 
welche und wie viele unterscheidet sich dabei ebenfalls je nach 
Controller. Die Anzahl ist dabei relevant um beim "durchblättern" der 
Konfigurationen zur Laufzeit nach dem letzten Element wieder von vorne 
anfangen zu können, oder andersrum, vor dem ersten Element wieder zum 
letzten zu springen. Ein statisches Festlegen der Anzahl wollte ich 
vermeiden, da das Anpassen beim Hinzufügen oder Entfernen von 
Konfigurationen schnell vergessen werden kann.

von Rolf M. (rmagnus)


Lesenswert?

Mikro 7. schrieb:
> Die implizite (böse) Redundanz ist aber ein Problem: Was wenn man sich
> verzählt hat oder wenn nachträglich die initializer list geändert wird?

Alleine die Tatsache, dass man überhaupt von Hand abzählen muss, stört 
schon. Wozu hat man den Computer?

von Mikro 7. (mikro77)


Lesenswert?

Rolf M. schrieb:
> Alleine die Tatsache, dass man überhaupt von Hand abzählen muss, stört
> schon. Wozu hat man den Computer?

Eine der Schwächen von C/++. "export" in C++20 verspricht zumindest 
etwas Besserung.

von A. S. (Gast)


Lesenswert?

Tim T. schrieb:
> Die Anzahl ist dabei relevant um beim "durchblättern" der
> Konfigurationen zur Laufzeit nach dem letzten Element wieder von vorne
> anfangen zu können, oder andersrum, vor dem ersten Element wieder zum
> letzten zu springen.

Beim ersten sind terminierte Listen praktisch, sie halten den Code 
einfach. Leider ist es fatal, wenn sie nicht terminiert sind.

Für einen "Roll-back" ist Deine Struct-Lösung deutlich besser. Von daher 
hast Du alles richtig gemacht.

Sobald Du das struct einmal hast, wirst Du es vermutlich mit weiteren 
Pointern füllen. Sei es auf Name oder SAP-Nummer der Platine, des 
Controllers oder dessen generelle Möglichkeiten (also jene Dinge, die 
nicht in jeder Konfiguration eines Controllers auftauchen müssen, 
sondern nur einmal pro Set)

Für Laufzeit und Speicher spielen Indirektionen in den meisten Fällen 
keine Rolle.

von Mombert H. (mh_mh)


Lesenswert?

Mikro 7. schrieb:
> Rolf M. schrieb:
>> Alleine die Tatsache, dass man überhaupt von Hand abzählen muss, stört
>> schon. Wozu hat man den Computer?
> Eine der Schwächen von C/++. "export" in C++20 verspricht zumindest
> etwas Besserung.
Soll es in C auch Module wie in C++ geben? Du meinst Module, wenn du 
"export" schreibst?

Für C++ macht das kaum einen Unterschied, da man jederzeit ein 
std::array benutzen könnte.

von A. S. (Gast)


Lesenswert?

Rolf M. schrieb:
> Alleine die Tatsache, dass man überhaupt von Hand abzählen muss, stört
> schon. Wozu hat man den Computer?

Naja, abzählen muss man ja nicht. Und solange jede Einheit einzeln 
kompiliert werden können soll, gibt es auch keine elegantere Lösung. 
Höchstens Pattern, die das vor dem Anwender verstecken.

(Und natürlich kann man das auch in C den Linker machen lassen, über 
Linker-Sections, ohne struct oder zählen. Aber wenn man den Code nicht 
lesen können will, kann man besser C++ nehmen)

von Mikro 7. (mikro77)


Lesenswert?

Mombert H. schrieb:
> Für C++ macht das kaum einen Unterschied, da man jederzeit ein
> std::array benutzen könnte.

Wie soll das mit std::array hier funktionieren?

von Rolf M. (rmagnus)


Lesenswert?

Mikro 7. schrieb:
> Mombert H. schrieb:
>> Für C++ macht das kaum einen Unterschied, da man jederzeit ein
>> std::array benutzen könnte.
>
> Wie soll das mit std::array hier funktionieren?

Das frage ich mich auch. Das Problem bleibt ja bestehen: Man muss im 
Header immer noch die Größe von Hand angeben.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Mikro 7. schrieb:
>> Mombert H. schrieb:
>>> Für C++ macht das kaum einen Unterschied, da man jederzeit ein
>>> std::array benutzen könnte.
>>
>> Wie soll das mit std::array hier funktionieren?
>
> Das frage ich mich auch. Das Problem bleibt ja bestehen: Man muss im
> Header immer noch die Größe von Hand angeben.

Nein. Die Größe ist Bestandteil des Typs in diesem Fall.

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
>> Das frage ich mich auch. Das Problem bleibt ja bestehen: Man muss im
>> Header immer noch die Größe von Hand angeben.
>
> Nein.

Dann zeig mal, wie du ein std::array deklarierst, ohne die Größe 
anzugeben.

> Die Größe ist Bestandteil des Typs in diesem Fall.

Das ist sie bei einem C-Array auch. Bei beiden muss ich sie bei einer 
Deklaration explizit angeben, damit der Compiler weiß, wie groß es ist, 
und zwar gerade weil die Größe Teil des Typs ist.
Konkret: Ob ich schreibe
1
extern const element_t element[3];
oder
1
extern const std::array<element_t, 3> element;
macht in der Hinsicht keinen Unterschied. Bei beiden steht die 3 
explizit da. Also hilft mir std::array dabei nicht.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Rolf M. schrieb:
> Also hilft mir std::array dabei nicht.

Dann nimm std::vector. Der kann für dich zählen.

Oliver

von Rolf M. (rmagnus)


Lesenswert?

Oliver S. schrieb:
> Rolf M. schrieb:
>> Also hilft mir std::array dabei nicht.
>
> Dann nimm std::vector. Der kann für dich zählen.

Dann aber natürlich komplett zur Laufzeit, mit dynamischer Allokation 
und allem, was da so dazu gehört.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Wilhelm M. schrieb:
>>> Das frage ich mich auch. Das Problem bleibt ja bestehen: Man muss im
>>> Header immer noch die Größe von Hand angeben.
>>
>> Nein.
>
> Dann zeig mal, wie du ein std::array deklarierst, ohne die Größe
> anzugeben.
>
>> Die Größe ist Bestandteil des Typs in diesem Fall.

> Also hilft mir std::array dabei nicht.

Mit CTAD kann die Größe und Elementtyp aus dem Initialisierer bestimmt 
werden. Und ein std::array zerfällt nicht.

von Mikro 7. (mikro77)


Lesenswert?

Einer der schönen Eigenschaften von C/++ sind die ausführlichen 
Prüfungen zur compile time. Wo viele Sprachen auf einen runtime error 
laufen, bietet C++ Möglichkeiten, einige davon bereits im Voraus 
auszuschließen.

In diesem Sinn ist die Frage des TO interessant, weil C/++ hier 
(initializer lists) durch seine fehlende "export" Möglichkeit und den 
dadurch notwendigen verkappten Header declarations an seine Grenzen 
stößt. Die Größe des Arrays ist zwar bekannt, man kann sie aber nicht 
(zur compile time) exportieren.

Natürlich gibt es eine Menge Möglichkeiten damit in der Praxis 
umzugehen. Die Größe händisch abzulegen ist wohl die praktikabelste (so 
wie der TO es macht).

von Mombert H. (mh_mh)


Lesenswert?

Rolf M. schrieb:
> Mikro 7. schrieb:
>> Mombert H. schrieb:
>>> Für C++ macht das kaum einen Unterschied, da man jederzeit ein
>>> std::array benutzen könnte.
>> Wie soll das mit std::array hier funktionieren?
> Das frage ich mich auch. Das Problem bleibt ja bestehen: Man muss im
> Header immer noch die Größe von Hand angeben.
1
#include <array>
2
struct element_t {
3
  int a;
4
  int b;
5
};
6
std::array element{element_t{1,2}, element_t{3,4}, element_t{5,6}};

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Mit CTAD kann die Größe und Elementtyp aus dem Initialisierer bestimmt
> werden.

Auch das geht mit einem klassischen C-Array, wenn auch ganz ohne fancy 
Akronym. Allerdings sollte dir eigentlich auch aufgefallen sein, dass es 
hier um die extern-Deklaration im Header geht, die vom Initializer 
nichts weiß.

von Mombert H. (mh_mh)


Lesenswert?

Mikro 7. schrieb:
> C/++
Was genau meinst du damit und warum schreibst du es so? Ich würde es 
ohne den umgebenden Text als "C ohne ++" lesen.

von Rolf M. (rmagnus)


Lesenswert?

Mombert H. schrieb:
>> Das frage ich mich auch. Das Problem bleibt ja bestehen: Man muss im
>> Header immer noch die Größe von Hand angeben.
> #include <array>
> struct element_t {
>   int a;
>   int b;
> };
> std::array element{element_t{1,2}, element_t{3,4}, element_t{5,6}};

Und wo ist jetzt die dazugehörige extern-Deklaration im Header, die ja 
eigentlich das Thema der Diskussion ist?
Hier nochmal das Problem des TO:

Tim T. schrieb:
> header.h:
1
typedef struct {
2
 const int a;
3
 const int b;
4
} element_t;
5
extern const element_t element[]; // <- Es geht um diese Zeile
> source.c:
1
#include "header.h";
2
const element_t element[] = { {1,2}, {3,4}, {5,6} };

: Bearbeitet durch User
von Mombert H. (mh_mh)


Lesenswert?

Rolf M. schrieb:
> Und wo ist jetzt die dazugehörige extern-Deklaration im Header, die ja
> eigentlich das Thema der Diskussion ist?
Dann schreib ein simples "constexpr inline" davor und pack die 
Definition in den Header.

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

Mombert H. schrieb:
> Rolf M. schrieb:
>> Und wo ist jetzt die dazugehörige extern-Deklaration im Header, die ja
>> eigentlich das Thema der Diskussion ist?
> Dann schreib ein simples "constexpr inline" davor und pack die
> Definition in den Header.

Und dann wird das Ding in 342 Quelldateien inkludiert und landet dann 
342 mal im finalen Binary? Ob das im Sinne des TO ist wage ich zu 
bezweifeln sonst hätte er das mit static const auch in C lösen können.

von Mombert H. (mh_mh)


Lesenswert?

Μαtthias W. schrieb:
> Mombert H. schrieb:
>> Rolf M. schrieb:
>>> Und wo ist jetzt die dazugehörige extern-Deklaration im Header, die ja
>>> eigentlich das Thema der Diskussion ist?
>> Dann schreib ein simples "constexpr inline" davor und pack die
>> Definition in den Header.
> Und dann wird das Ding in 342 Quelldateien inkludiert und landet dann
> 342 mal im finalen Binary? Ob das im Sinne des TO ist wage ich zu
> bezweifeln sonst hätte er das mit static const auch in C lösen können.
Nein, es landet eben nicht 342 mal im Binary. Das hat damit zu tun, dass 
"constexpr inline" etwas anderes ist als "static const" ...

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Und wo ist jetzt die dazugehörige extern-Deklaration im Header, die ja
> eigentlich das Thema der Diskussion ist?

Die brauchen wir nicht mehr, wenn wir die kleinen Goodies von C++ 
verwenden.
Folgende Header-Datei:
1
#pragma once
2
3
#include <array>
4
5
struct Config {
6
    struct element_t {
7
  int a;
8
  int b;
9
    };
10
    inline static constexpr std::array elements = {element_t{1, 2}, element_t{3, 4}};    
11
};

Und dann in den Implementierungsdateien:
1
#include "bm12.h"
2
3
4
int foo() {    
5
    for(const auto& e : Config::elements) {        
6
    }
7
}

inline ist ein oft missverstandenes Schlüsselwort: es bedeutet, dass 
eine Verletzung der ODR durch den Linker "repariert" wird (einfach 
gesprochen).

von DPA (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Und ein std::array zerfällt nicht.

Wann will man das denn effektiv? Einfaches Szenario, ich will das 
std::array an eine Funktion übergeben. Entweder die müssen immer gleich 
gross sein, und ich muss die Grösse wissen, oder ich muss ein Template 
nutzen, und habe dann vermutlich unnötigen Code. Oder ich übergebe halt 
doch was anderes, als ein std::array, irgend ein Iterator oder so...

von Wilhelm M. (wimalopaan)


Lesenswert?

DPA schrieb:
> Wilhelm M. schrieb:
>> Und ein std::array zerfällt nicht.
>
> Wann will man das denn effektiv? Einfaches Szenario, ich will das
> std::array an eine Funktion übergeben. Entweder die müssen immer gleich
> gross sein, und ich muss die Grösse wissen, oder ich muss ein Template
> nutzen, und habe dann vermutlich unnötigen Code. Oder ich übergebe halt
> doch was anderes, als ein std::array, irgend ein Iterator oder so...

Immer will man das. Es sei denn, der DT der Elemente ermöglichen ein 
Sentinel.
Ansonsten wäre man ja wieder bei dem C Scheiß.

Einige Anwendungsfälle erfordern ein Array einer vorgegebenen Größe (und 
ggf. Elementtyp), dann ist es auch ein Vorteil, weil es bei einem 
Mismatch zw. Argument- und Parametertyp nicht compiliert (im Gegensatz 
zu C).

Siehe auch Algorithmen: die Algorithmen sind üblicherweise templates und 
haben eine Schnittstelle mit

- Container,
- Iteratorpaar oder mehr,
- Range(s)

Und der angenommene Code-Bloat ist ein viel genanntes Pseudo-Argument, 
was durch die bessere Optimierung oft (mehr als) wett gemacht wird.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Immer will man das. Es sei denn, der DT der Elemente ermöglichen ein
> Sentinel. Ansonsten wäre man ja wieder bei dem C Scheiß.

Wenn die Funktion std::arrays mit unterschiedlichen Größen verarbeiten 
können soll, bleibt mir wie DPA schon schreibt neben einem Template nur 
die Möglichkeit, sowas wie einen Iterator zu nehmen. Wenn ich das tue, 
bin ich aber tatsächlich da angelangt, wo C ist, denn das ist dann im 
Prinzip das gleiche wie der Zeiger auf den Anfang des Arrays. An die 
Größe komme ich wieder nicht heran, sofern sie nicht separat übergeben 
wird.

> Und der angenommene Code-Bloat ist ein viel genanntes Pseudo-Argument,
> was durch die bessere Optimierung oft (mehr als) wett gemacht wird.

Wo ich da eher Bloat sehe, ist beim Compilieren. Das dauert bei sehr 
template-lastigem Code gerne mal ewig, was die Entwicklung verlangsamt, 
weil ich ständig auf den Compiler warten muss. Dazu kommen die extrem 
gesprächigen und sehr viel schwieriger zu lesenden 
Compiler-Fehlermeldungen.
Templates sind ein cooles Feature, aber ich will nicht mein ganzes 
Programm als großes Template schreiben.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Wilhelm M. schrieb:
>> Immer will man das. Es sei denn, der DT der Elemente ermöglichen ein
>> Sentinel. Ansonsten wäre man ja wieder bei dem C Scheiß.
>
> Wenn die Funktion std::arrays mit unterschiedlichen Größen verarbeiten
> können soll, bleibt mir wie DPA schon schreibt neben einem Template nur
> die Möglichkeit, sowas wie einen Iterator zu nehmen.

Auch dann wird die Funktion i.A. ein Template über den Elementtyp sein.

> Wenn ich das tue,
> bin ich aber tatsächlich da angelangt, wo C ist, denn das ist dann im
> Prinzip das gleiche wie der Zeiger auf den Anfang des Arrays.

Nein. Ein Zeiger ist zwar ein Iterator, aber nicht jeder Iterator ist 
ein Zeiger.

Der Aufrufer hat oft die Aufgabe, das halb-offene Intervall anzupassen.

Ansonsten unterstützen Iteratoren (nicht alle, je nach Typ) den op-(), 
und damit kann man die Elementanzahl ermitteln. Dies ist aber in den 
meisten Fällen nicht wichtig, wenn man Iteratoren nimmt, da die ja das 
Nutzintervall bestimmen.

Andernfalls nimmt man std::span<> o.ä. (std::stringview).

> An die
> Größe komme ich wieder nicht heran, sofern sie nicht separat übergeben
> wird.

Quatsch, s.o.

>> Und der angenommene Code-Bloat ist ein viel genanntes Pseudo-Argument,
>> was durch die bessere Optimierung oft (mehr als) wett gemacht wird.
>
> Wo ich da eher Bloat sehe, ist beim Compilieren. Das dauert bei sehr
> template-lastigem Code gerne mal ewig, was die Entwicklung verlangsamt,
> weil ich ständig auf den Compiler warten muss. Dazu kommen die extrem
> gesprächigen und sehr viel schwieriger zu lesenden
> Compiler-Fehlermeldungen.

Da hat sich (z.B. auch bei clang) extrem viel getan. Es ist seit einigen 
Jahren längst nicht mehr so wie anno-dazumals.

Und lieber Fehlermeldungen des Compilers lesen als langes 
Laufzeit-Debugging.

> Templates sind ein cooles Feature, aber ich will nicht mein ganzes
> Programm als großes Template schreiben.

Warum nicht?
Für die meisten µC Sachen mache ich das und es funktioniert absolut 
hervorragend.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Endlich ist die -1 Bewertung da, ich dachte schon, Du schläfst ;-)

von Mikro 7. (mikro77)


Lesenswert?

Wilhelm M. schrieb:
> inline ist ein oft missverstandenes Schlüsselwort: es bedeutet, dass
> eine Verletzung der ODR durch den Linker "repariert" wird (einfach
> gesprochen).

Nette Idee static constexpr im class scope für ODR zu "missbrauchen". 
:-)

Das funktioniert aber auch ohne inline und ohne CTAD.

Statt
1
inline static constexpr std::array elements = {element_t{1, 2}, element_t{3, 4}};
funktioniert das folgende an gleicher Stelle genauso:
1
static constexpr element_t elements[] = { {1,2}, {3,4} };

von Wilhelm M. (wimalopaan)


Lesenswert?

Mikro 7. schrieb:
> Nette Idee static constexpr im class scope für ODR zu "missbrauchen".
> :-)

static constexpr ist implizit inline: sorry, Fehler von mir ;-)

: Bearbeitet durch User
von Mikro 7. (mikro77)


Lesenswert?

Ich meinte C++11: Durch den "struct Config" scope wird ODR sicher 
gestellt und man kann quasi im Header initialisieren (im Source 
braucht es dann nur noch eine Dummy Definition).

In C++17 (inline) brauchst du den "struct Config" scope doch gar nicht, 
oder wofür soll der dort gut sein?

von ein Pythonexperte (Gast)


Lesenswert?

Wie die C++-Fanboys überfordert sind ;-) Einfach nur herrlich.

von foobar (Gast)


Lesenswert?

source.h und header.h wie im Eingangspost.

main.c:
1
#include "header.h"
2
#include "source.sizes"
3
4
int anzahl_elemente = sizeof_element / sizeof(element_t);
Makefile:
1
%.sizes: %.o
2
        nm -Pg $< | awk '$$2=="D" { print "#define sizeof_"$$1" 0x"$$4 }' >$@
3
4
main.o: header.h source.sizes

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Also wenn schon von hinten durch die Brust ins Auge, dann richtig:

header.h:
1
#ifndef _HEADER_H_
2
#define _HEADER_H_
3
4
typedef struct {
5
  const int a;
6
  const int b;
7
} element_t;
8
9
#endif

source.h:
1
#ifndef _SOURCE_H_
2
#define _SOURCE_H_
3
4
#define ELEMENTE { {1,2}, {3,4}, {5,6} }
5
6
#endif

main.c:
1
#include <stdio.h>
2
#include "header.h"
3
#include "source.h"
4
5
const element_t element[] = ELEMENTE;
6
7
int main(void) {
8
9
  printf("Das Feld hat %d Elemente\n", sizeof(element) / sizeof(element_t));
10
  printf("Das erste Element hat die Werte %d und %d\n", element[0].a, element[0].b);
11
  printf("Das zweite Element hat die Werte %d und %d\n", element[1].a, element[1].b);
12
  printf("Das dritte Element hat die Werte %d und %d\n", element[2].a, element[2].b);
13
14
  return 0;
15
}

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.