Größe eines externen Arrays in C

Gast #7153185
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...
#7153239
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
Gast #7153244
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.
Gast #7153276
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.
Angehängte Dateien:
#7153295
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.
Gast #7153418
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.
#7153521
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.
#7153657
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.
#7153688
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.
#7154112
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).
Gast #7154222
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.
#7154226
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.
Gast #7154239
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
#7154240
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!
Gast #7154246
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.
#7154256
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.
#7154268
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
Gast #7154284
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]);
#7154290
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).
Beitrag #7154305 wurde von einem Moderator gelöscht.
#7154314
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.)
Gast #7154343
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.
Gast #7154366
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.
#7154406
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.
Gast #7154416
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)
#7154555
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.
#7154599
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.
#7154654
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.
#7154655
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).
#7154739
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}};
#7154744
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} };
Persönliche Seite #7154828
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.
#7154837
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" ...
#7154865
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).
Gast #7154950
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...
#7154964
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.
#7154997
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.
#7155013
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.
#7155046
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} };
#7155169
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?
Gast #7155188
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
Gast #7156511
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
}

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren