Forum: Compiler & IDEs Merkwürdiges C-Konstrukt


von Mike (Gast)


Lesenswert?

In einem Displaytreiber findet sich folgender C-Code:
1
typedef struct {
2
    uint8_t length; //length of data minus the command byte
3
    uint8_t data[]; //first byte is always the command byte
4
} static_transfer_t;
5
...
6
static const static_transfer_t INIT_04 = { .length = 3, {cmd_BOOSTER_SOFT_START_CONTROL, 0xD7, 0xD6, 0x9D} }; //magic values from datasheet
7
static const static_transfer_t INIT_05 = { .length = 1, {cmd_SET_GATE_TIME, 0x08} }; //magic value: 2us per line

Dass da eine konstante Struktur mit dem Byte length und dem Array data 
initialisiert wird, verstehe ich. Aber das Konstrukt .length = ... ? 
habe ich noch nie gesehen, obwohl ich seit über 20 Jahren in C 
programmiere. Legt das die Länge des folgenden data[]-Feldes fest?

von Alt G. (altgr)


Lesenswert?

Das legth hat keinerlei einfluss auf die länge des array.
Du kannst lenth=1 und 10 array elemente initialisieren.

Das wird zum vereinfachten auslesen so gemacht sein.

von Thomas H. (thomash2)


Lesenswert?

Mike schrieb:
> In einem Displaytreiber findet sich folgender C-Code:
> typedef struct {
>     uint8_t length; //length of data minus the command byte
>     uint8_t data[]; //first byte is always the command byte
> } static_transfer_t;
> ...
> static const static_transfer_t INIT_04 = { .length = 3,
> {cmd_BOOSTER_SOFT_START_CONTROL, 0xD7, 0xD6, 0x9D} }; //magic values
> from datasheet
> static const static_transfer_t INIT_05 = { .length = 1,
> {cmd_SET_GATE_TIME, 0x08} }; //magic value: 2us per line
>
> Dass da eine konstante Struktur mit dem Byte length und dem Array data
> initialisiert wird, verstehe ich. Aber das Konstrukt .length = ... ?
> habe ich noch nie gesehen, obwohl ich seit über 20 Jahren in C
> programmiere. Legt das die Länge des folgenden data[]-Feldes fest?

ich würde es eher von INIT_04 und INIT_05 abhängig machen. .length sagt 
nichts aus.

uint8_t data[]; entspricht 0 Bytes, wenn man das struct nun über einen 
Speicherbereich castet kann man sehr wohl auf einzelne Elemente 
zugreifen.

von Dirk B. (dirkb2)


Lesenswert?

Die Möglichkeit den Member einer struct/union bei der Initialisierung 
anzugeben gibt es seit C99 (also 23 Jahre). Davor war es u.A. eine 
gcc-Erweiterung.

https://en.cppreference.com/w/c/language/struct_initialization

von Thomas H. (thomash2)


Lesenswert?

habe blödsinn geschrieben, da ich was übersehen habe.
Dirk hat recht, das geht schon lange (vor allem bei GCC, bei anderen 
Compilern sieht's zum Teil aber anders aus).

von Olaf (Gast)


Lesenswert?

> Dirk hat recht, das geht schon lange

Das mag sein. Ich programmiere auch schon laenger in C. Mein
erster C-Compiler war BDS-C unter CP/M im Kinderzimmer,
aber ich sehe das jetzt auch zum erstenmal.

Hat sich wohl nicht so recht durchgesetzt. :)

Olaf

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

Olaf schrieb:
>> Dirk hat recht, das geht schon lange
>
> Das mag sein. Ich programmiere auch schon laenger in C. Mein
> erster C-Compiler war BDS-C unter CP/M im Kinderzimmer,
> aber ich sehe das jetzt auch zum erstenmal.
>
> Hat sich wohl nicht so recht durchgesetzt. :)

Weil viele das nicht kennen. Z.B. im Linuxkernel wird das überall 
verwendet und erleichtert dort die wartbarkeit enorm. Auch C++ kennt 
mittlerweile eine vereinfachte (und weit weniger nützliche) Form.

Matthias

von DPA (Gast)


Lesenswert?

Nennt sich Designated Initializers.

von Olaf (Gast)


Lesenswert?

Ich dachte bisher Magicvalues im Source sind baeh!

Olaf

von Mike (Gast)


Lesenswert?

DPA schrieb:
> Nennt sich Designated Initializers.

Danke, mit C99 habe ich mich noch nie richtig beschäftigt. In unserer 
Firma ist immer noch C90 aktuell. Die Initialisierung dürfte vor allem 
bei großen Strukturen für mehr Übersichtlichkeit sorgen, wirklich nötig 
ist sie natürlich nicht.

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

Mike schrieb:
> DPA schrieb:
>> Nennt sich Designated Initializers.
>
> Danke, mit C99 habe ich mich noch nie richtig beschäftigt. In unserer
> Firma ist immer noch C90 aktuell. Die Initialisierung dürfte vor allem
> bei großen Strukturen für mehr Übersichtlichkeit sorgen, wirklich nötig
> ist sie natürlich nicht.

Nicht nur die Übersicht wird besser. Man kann auch problemlos neue 
Strukturelement einfügen (auch in der Mitte) oder die Reihenfolge ändern 
ohne das sich die Initialisierung ändert bzw. angepasst werden muss. Bei 
der Reihenfolge versagt übrigens die C++ Variante

Matthias

von jojo (Gast)


Lesenswert?

Hier nochmal ein netter Thread dazu: 
https://stackoverflow.com/questions/47202557/what-is-a-designated-initializer-in-c

Designated Initializer sind ein Segen, wenn es um struct inits geht, vor 
allem, wenn man viel in structs kapselt ("Instanzen" z.B., um OOP 
nachzuahmen).

Nie mehr ohne ;)

von 🐧 DPA 🐧 (Gast)


Lesenswert?

Ich nutze es auch manchmal noch, um Named Parameter & optionale 
Parameter bei Funktionen zu simulieren:
1
struct myfunction_args {
2
  int x;
3
  int bla;
4
  const char* blup;
5
};
6
#define myfunction(...) myfunction_p((struct myfunction_args){__VA_ARGS__})
7
8
int main(){
9
  myfunction(1, .blup="Hello World!")
10
}

Ist zwar nicht für alle Situationen geeignet, und ein bisschen ein Hack, 
aber manchmal ist das einfach unendlich praktisch.

(PS: Es ist im Standard geregelt, dass bei Mehrfachinitialisierung die 
letzte gewinnt, was für defaults auch praktisch ist. Aber wenn man das 
nutzen will, muss man beim Compiler die Warnung deswegen abstellen. Und 
das geht leider nicht nur für ein einzelnes Macro...)

von Wilhelm M. (wimalopaan)


Lesenswert?

Μαtthias W. schrieb:

> Nicht nur die Übersicht wird besser. Man kann auch problemlos neue
> Strukturelement einfügen (auch in der Mitte) oder die Reihenfolge ändern
> ohne das sich die Initialisierung ändert bzw. angepasst werden muss. Bei
> der Reihenfolge versagt übrigens die C++ Variante

Versagen würde ich das nicht nennen: es müssen die Regeln zur 
(Aggregate)-Initialisierung eingehalten werden, d.h. die Elemente werden 
in der Reihenfolge der Deklaration initialisiert. Leider gibt es bei der 
Initialisierungsliste eines ctors dazu nur eine Warnung. Bei 
designated-initializer ist es dann sinnvollerweise ein Fehler.

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Versagen würde ich das nicht nennen: es müssen die Regeln zur
> (Aggregate)-Initialisierung eingehalten werden, d.h. die Elemente werden
> in der Reihenfolge der Deklaration initialisiert. Leider gibt es bei der
> Initialisierungsliste eines ctors dazu nur eine Warnung. Bei
> designated-initializer ist es dann sinnvollerweise ein Fehler.

Aus Sicht des C++ Experten ist es eine Einhaltung von Regeln wofür es 
sicher gute Gründe gibt. Macht das Feature leider weniger praktisch als 
in C. Aber  immerhin gibt es das jetzt auch in C++ wenn auch mit 
Einschränkungen.

Matthias

von Wilhelm M. (wimalopaan)


Lesenswert?

🐧 DPA 🐧 schrieb:
> (PS: Es ist im Standard geregelt, dass bei Mehrfachinitialisierung die
> letzte gewinnt, was für defaults auch praktisch ist. Aber wenn man das
> nutzen will, muss man beim Compiler die Warnung deswegen abstellen. Und
> das geht leider nicht nur für ein einzelnes Macro...)

Kannst aber

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wirgendwas"
...
#pragma GCC diagnostic pop

mit in Dein Macro aufnehmen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Als besonders praktisch empfinde ich die Designated Initializers für die
Initialisierung dünn besetzter Arrays:
1
int array[100] = {
2
  [ 2] = 123,
3
  [13] = 456,
4
  [75] = 789
5
};

Dafür gibt es wirklich nicht viele Alternativen außer

- viele Nullen tippen oder

- eine Initialisierungsfunktion schreiben,

was aber beides nicht sehr elegant ist.

von Wilhelm M. (wimalopaan)


Lesenswert?

Yalu X. schrieb:
> Als besonders praktisch empfinde ich die Designated Initializers für die
> Initialisierung dünn besetzter Arrays:
>
>
1
> int array[100] = {
2
>   [ 2] = 123,
3
>   [13] = 456,
4
>   [75] = 789
5
> };
6
>
>
> Dafür gibt es wirklich nicht viele Alternativen außer
>
> - viele Nullen tippen oder
>
> - eine Initialisierungsfunktion schreiben,
>
> was aber beides nicht sehr elegant ist.

Das ist etwas, was ich so auch nutze und ganz praktisch finde.

In C++ (ja, C++, das wurde oben von jemand anderem ja schon 
angesprochen, deswegen gehe ich nochmal darauf ein, und ja: bitte die 
negative Bewertung nicht vergessen) nimmt man dazu dann eben Lambdas als 
IIFE:
1
auto a = []{
2
    std::array<int, 100> data;
3
    data[ 2] = 123;
4
    data[13] = 456;
5
    data[75] = 789;
6
    return data;
7
}();

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

Hi

Ich hab das mal in godbolt.org gekippt

C:
https://godbolt.org/z/37s348aae

C++:
https://godbolt.org/z/15n5MhzKK

Da sieht man schön das f in C und C++ den exakt gleichen Code produziert 
trotz der Verwendung von std::array. Da macht der Compiler einen guten 
Job. Die Initialisierung braucht aber etwas mehr Code. Auf der C Seite 
ist das ganze in der .code Initialisierung versteckt. C++ muss es 
explizit machen was wohl beim Programmstart etwas Laufzeit und auch 
etwas Programmspeicher kostet. Aber garnicht schlecht was der C++ 
Compiler da draus macht wenn man sich mal die Komplexität des Header 
<array> zu Gemüte führt.

Matthias

von Wilhelm M. (wimalopaan)


Lesenswert?

Μαtthias W. schrieb:
> Da sieht man schön das f in C und C++ den exakt gleichen Code produziert
> trotz der Verwendung von std::array. Da macht der Compiler einen guten
> Job. Die Initialisierung braucht aber etwas mehr Code. Auf der C Seite
> ist das ganze in der .code Initialisierung versteckt. C++ muss es
> explizit machen was wohl beim Programmstart etwas Laufzeit und auch
> etwas Programmspeicher kostet

Hat weniger was mit C++ als mit dem Compiler zu tun: clang ist da für 
ARM und x86 besser.

avr-g++ macht es auch besser (<array> schreibt man sich gerade selbst 
oder nimmt es von irgendwoher):
1
main:
2
ldi r24,0                ;
3
ldi r25,0                ;
4
ret
5
.size   main, .-main
6
.data
7
.type   a, @object
8
.size   a, 200
9
a:
10
.zero   4
11
.word   123
12
.zero   20
13
.word   456
14
.zero   122
15
.word   789
16
.zero   48

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

🐧 DPA 🐧 schrieb:
> Ich nutze es auch manchmal noch, um Named Parameter & optionale
> Parameter bei Funktionen zu simulieren:

interessant!

von Rolf M. (rmagnus)


Lesenswert?

Mike schrieb:
> Danke, mit C99 habe ich mich noch nie richtig beschäftigt. In unserer
> Firma ist immer noch C90 aktuell.

Dabei ist auch C99 schon lange nicht mehr die aktuelle Version.

Wilhelm M. schrieb:
> Leider gibt es bei der Initialisierungsliste eines ctors dazu nur eine
> Warnung.

Ich würde sagen: Leider gibt es eine Warnung. Die nervt mich nur. Ich 
weiß auch so, dass die Elemente in der Reihenfolge der Deklaration 
initialisiert werden. Das in der Initialisierungsliste in einer anderen 
Reihenfolge zu schreiben, ist ja erstmal kein Fehler. Warum sollte das 
also mehr als eine Warnung sein? In der weit überwiegenden Zahl der 
Fälle ist mir die Initialisierungsreihenfolge sowieso egal, weil die 
Elemente nicht von einander abhängig sind. Da stört es nur, wenn man 
dann die Fleißarbeit hat, die Reihenfolge an beiden Stellen immer 
unnötigerweise synchron halten zu müssen, um diese blöde Warnung 
wegzubekommen.

> Bei designated-initializer ist es dann sinnvollerweise ein Fehler.

Ob das sinnvoll ist, sei dahingestellt. Es kommt leider auch sogar bei 
Strukturen, die nicht mal einen explizit definierten Konstruktor haben 
oder gar rein POD sind, wo die Reihenfolge nun wirklich keine Rolle 
spielt.
Dieser Code z.B. geht in C, führt in C++ aber zu einem Fehler, weil die 
Reihenfolge nicht stimmt:
1
struct foo
2
{     
3
    int a;
4
    int b;
5
};
6
  
7
struct foo f =
8
{
9
    .b = 5,
10
    .a = 3
11
};
Aber dass die Reihenfolge nicht strikt eingehalten werden muss, ist 
einer der wesentlichen Vorteile von designated initializiers.
Genau dieser Fall hat im übrigen dazu geführt, dass ich einigen Code 
ändern musste, als ich ihn von C auf C++ umgestellt habe. Ich hab die 
designated initializers dort viel genutzt, weil ich es einfacher finde, 
mir die Namen der Elemente zu merken als deren Reihenfolge. In C++ 
brauche ich mit den designated initializers dann beides.

Wilhelm M. schrieb:
> In C++ (ja, C++, das wurde oben von jemand anderem ja schon
> angesprochen, deswegen gehe ich nochmal darauf ein, und ja: bitte die
> negative Bewertung nicht vergessen) nimmt man dazu dann eben Lambdas als
> IIFE:
> auto a = []{
>     std::array<int, 100> data;
>     data[ 2] = 123;
>     data[13] = 456;
>     data[75] = 789;
>     return data;
> }();

Das ist dann aber keine Initialisierung mehr, sondern eine Zuweisung 
nach Default-Initialisierung.

Μαtthias W. schrieb:
> Da sieht man schön das f in C und C++ den exakt gleichen Code produziert
> trotz der Verwendung von std::array.

Wieso "trotz"? std::array macht ja zur Laufzeit eigentlich nichts. Ist 
alles nur Template-Zeug, dass zur Compilezeit aufgelöst wird.

: Bearbeitet durch User
von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Μαtthias W. schrieb:
>
>> Da sieht man schön das f in C und C++ den exakt gleichen Code produziert
>> trotz der Verwendung von std::array.
>
> Wieso "trotz"? std::array macht ja zur Laufzeit eigentlich nichts. Ist
> alles nur Template-Zeug, dass zur Compilezeit aufgelöst wird.

Weil das ganze Templatezeug eben reichlich komplex ist und das aus 
meiner Sicht für den Compiler bzw. deren Entwickler erheblich komplexer 
ist das zum gleichen Code aufzulösen wie die C Variante. Aber von 
Compilerbau hab ich auch wirklich keine Ahnung.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Wilhelm M. schrieb:
>> In C++ (ja, C++, das wurde oben von jemand anderem ja schon
>> angesprochen, deswegen gehe ich nochmal darauf ein, und ja: bitte die
>> negative Bewertung nicht vergessen) nimmt man dazu dann eben Lambdas als
>> IIFE:
>> auto a = []{
>>     std::array<int, 100> data;
>>     data[ 2] = 123;
>>     data[13] = 456;
>>     data[75] = 789;
>>     return data;
>> }();
>
> Das ist dann aber keine Initialisierung mehr, sondern eine Zuweisung
> nach Default-Initialisierung.

Nein, es ist eine Initialisierung (es geht um a, nicht um data).
Schreib 'const a' und Du stellst fest, dass es eine Initialisierung ist.

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
>> Das ist dann aber keine Initialisierung mehr, sondern eine Zuweisung
>> nach Default-Initialisierung.
>
> Nein, es ist eine Initialisierung

Ok, es ist eine Copy-Initialisierung nach Default-Initialisierung und 
Zuweisung.

> (es geht um a, nicht um data).

Mir geht es um den Elementtyp. Wenn das kein einfacher int ist, sondern 
eine Klasse mit Konstruktor, macht es durchaus einen Unterschied.

von Wilhelm M. (wimalopaan)


Lesenswert?

Μαtthias W. schrieb:
> Rolf M. schrieb:
>> Μαtthias W. schrieb:
>>
>>> Da sieht man schön das f in C und C++ den exakt gleichen Code produziert
>>> trotz der Verwendung von std::array.
>>
>> Wieso "trotz"? std::array macht ja zur Laufzeit eigentlich nichts. Ist
>> alles nur Template-Zeug, dass zur Compilezeit aufgelöst wird.
>
> Weil das ganze Templatezeug eben reichlich komplex ist und das aus
> meiner Sicht für den Compiler bzw. deren Entwickler erheblich komplexer
> ist das zum gleichen Code aufzulösen wie die C Variante. Aber von
> Compilerbau hab ich auch wirklich keine Ahnung.

Es ist eigentlich sogar üblich, dass Templates zu besserem (Peformance) 
Code führen, weil Funktionstemplates implizit inline sind 
(notwendigerweise sein müssen), der Compiler daher besser optimieren 
kann. Daher werden oft keine Funktionsaufrufe generiert und damit kann 
besser über Funktionsgrenzen (bei Templatefunktionen oder anderen inline 
Funktionen) hinweg optimiert werden.
Natürlich kann - je nach der Anzahl der unterschiedlichen 
Instanziierungen des Templates - die Code-Größe steigen. Aber auch das 
ist meistens viel geringer als befürchtet (code-bloat).

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Ok, es ist eine Copy-Initialisierung nach Default-Initialisierung und
> Zuweisung.

In der Praxis haben wir hier copy-elision durch NRVO. Das ist zwar nicht 
zwingend (so wie etwa die RVO), findet aber doch gerade bei diesem 
Pattern (init with IIFE) (fast) immer statt.

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> In der Praxis haben wir hier copy-elision durch NRVO.

Das ist schon klar. Wie gesagt geht es mir aber nicht darum, wie a 
initialisiert wird, sondern darum, dass die Elemente alle nicht direkt 
per Initialisierung ihren Wert bekommen, sondern per Zuweisung nach 
einer Default-Initialisierung. Wobei nach Eliminierung aller 
Kopieraktionen das am Ende quasi auch zur Initialisierung von a wird. 
Füge einfach mal bei
1
std::array<int, 100> data;
ein const vor dem int ein. Dann siehst du, was ich meine.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Wilhelm M. schrieb:
>> In der Praxis haben wir hier copy-elision durch NRVO.
>
> Das ist schon klar. Wie gesagt geht es mir aber nicht darum, wie a
> initialisiert wird,

Mir aber ging es nur darum, und zwar als Ersatz für das vorher genannte:
1
const int a[100] = {
2
  [ 2] = 123,
3
  [13] = 456,
4
  [75] = 789
5
};

Denn diese non-trivial designated-initializer sind nicht in C++ möglich.
Aber: es geht eben mit IIFE und ohne Mehraufwand. Zudem bietet die IIFE 
in C++ mehr Möglichkeiten wie etwa non-const Initialisierer, was bei C 
wieder nicht funktioniert. Make your choice ...

> Füge einfach mal bei
>
1
> std::array<int, 100> data;
2
>
> ein const vor dem int ein. Dann siehst du, was ich meine.

Das habe ich schon verstanden: das ist aber sinnlos.

Es geht um die Form:
1
const auto a = []{
2
    std::array<int, 100> data;
3
    data[ 2] = 123;
4
    data[13] = 456;
5
    data[75] = 789;
6
    return data;
7
}();

Dies ist ohne IIFE schlecht machbar (Initialisierung es ro-Objektes)

von Peter D. (peda)


Lesenswert?

Ungebräuchlich ist die gemischte Initialisierung.
Entweder man verläßt sich nur auf die Reihenfolge oder man gibt für alle 
Member deren Namen an.

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.