Forum: PC-Programmierung Komplexe typedef struct mit 0 initialisieren


von Zorro (Gast)


Lesenswert?

Hi, ich habe ein weiteres Problem.

WIe kann ich eine typedef struct das aus mehrern typedef structs besteht 
alle Variablen auf 0 initialisieren?

von DerEgon (Gast)


Lesenswert?

Ein typedef ist nur eine Typvereinbarung.

Wenn Du eine Variable dieses Typs anlegst, kannst Du sie entweder so 
initialisieren, wie man das mit dem zugrundelegenden Typ macht (in 
Deinem Fall also einer Initialisierungsliste für Deine Struktur), oder 
aber, Du kannst jedes einzelne Byte, aus dem die Variable besteht, auf 0 
setzen:

1
typedef struct 
2
{ 
3
  int x;
4
  int y;
5
  int z;
6
} blafusel;
7
8
9
blafusel meineVariable = { 0, 0, 0 };
10
11
blafusel meineAndereVariable;
12
13
memset(&meineAndereVariable, 0, sizeof (meineAndereVariable));

von Zorro (Gast)


Lesenswert?

Danke dir

von Wilhelm M. (wimalopaan)


Lesenswert?

DerEgon schrieb:
> Ein typedef ist nur eine Typvereinbarung.

Nein, ein typedef ist ein Typ-Alias. Entgegen dem Namen deklariert 
typedef keinen distinkten DT.

von DerEgon (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Nein, ein typedef ist ein Typ-Alias. Entgegen dem Namen deklariert
> typedef keinen distinkten DT.

Ein typedef verhält sich aber wie ein Typ, insofern ist die 
Unterscheidung ziemlich irrelevant.

von Wilhelm M. (wimalopaan)


Lesenswert?

DerEgon schrieb:
> Wilhelm M. schrieb:
>> Nein, ein typedef ist ein Typ-Alias. Entgegen dem Namen deklariert
>> typedef keinen distinkten DT.
>
> Ein typedef verhält sich aber wie ein Typ, insofern ist die
> Unterscheidung ziemlich irrelevant.

Nö, das ist sehr relevant. Aber für Programmierer für die alles ein int 
ist, wenn es kein String sein kann, magst Du Recht haben ...

von DerEgon (Gast)


Lesenswert?

Dann erzähl doch mal.

von (prx) A. K. (prx)


Lesenswert?

In der Sprachsyntax von C ist etwas wie die Typedef-Namen eigentlich 
überhaupt nicht sauber möglich. Einer der Geburtsfehler der Syntax, denn 
ursprünglich gab es keine typedefs.

Denn die üblichen Typnamen wie "int" sind Keywords, und das nicht ohne 
Grund. Weshalb Typedef-Namen ebenfalls die Rolle von Keywords übernehmen 
müssen, aber natürlich nur so halb, andernfalls wären sie beispielsweise 
nicht als Strukt-Namen möglich. Ja nach syntaktischem Kontext werden 
Typedef-Namen vom Lexer als quasi-Keywords oder als normale Identifier 
betrachtet.

Wer Sinn für formale Syntax hat, kriegt bei sowas die Krise. Aber so ist 
es eben und man lebt damit.

: Bearbeitet durch User
von Mombert H. (mh_mh)


Lesenswert?

DerEgon schrieb:
> Dann erzähl doch mal.

Ich übernehme das mal für Wilhelm.
1
typedef int zeit_alias_t;
2
typedef int abstand_alias_t;
3
void berechne_geschwindigkeit_alias(zeit_alias_t, abstand_alias_t);
4
5
void test1() {
6
    zeit_alias_t z = 42;
7
    abstand_alias_t a = 1;
8
    berechne_geschwindigkeit_alias(a, z); // <--- ups
9
}
10
11
struct zeit {
12
    int wert;
13
};
14
struct abstand {
15
    int wert;
16
};
17
void berechne_geschwindigkeit(struct zeit, struct abstand);
18
19
void test2() {
20
    struct zeit z = {42};
21
    struct abstand a = {1};
22
    berechne_geschwindigkeit(a, z); // <--- ahhh
23
}
test1 akzeptiert der Compiler klaglos, weil er zwei ints erwartet und 
zwei bekommt. Es sind eben nur Aliasse und keine neuen Typen. In test2 
wehrt sich der Compiler, weil er nicht die richtigen Typen bekommt.

von Wilhelm M. (wimalopaan)


Lesenswert?

Mombert H. schrieb:
> DerEgon schrieb:
>> Dann erzähl doch mal.
>
> Ich übernehme das mal für Wilhelm.

Danke!

Ich hätte es so gezeigt:
1
typedef char CC;
2
3
static_assert(std::is_same_v<char, CC>);

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Ich hätte es so gezeigt:1typedef char CC;
> 23static_assert(std::is_same_v<char, CC>);

War klar, ist aber wie immer für alle außerhalb der „++“-Welt nicht 
geeignet.

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> Wilhelm M. schrieb:
>> Ich hätte es so gezeigt:1typedef char CC;
>> 23static_assert(std::is_same_v<char, CC>);
>
> War klar, ist aber wie immer für alle außerhalb der „++“-Welt nicht
> geeignet.

Im Eingangspost steht nichts von "nur C".

von A. S. (Gast)


Lesenswert?

Zorro schrieb:
> WIe kann ich eine typedef struct das aus mehrern typedef structs besteht
> alle Variablen auf 0 initialisieren?

 structs in structs werden mit nested {} initialisiert.

Du kannst in C alle "folgenden" Elemente weglassen, die mit 0 
initialisiert werden sollen.

ja, ein typedef hat nichts mit structs zu tun. Ein typedef verschleiert 
da nur / spart tipparbeit.


Beispiel ohne typedef.
1
struct sBasic {int b1, int b2};
2
3
struct sExt{int a, struct sBasic b, int c};
4
5
struct sExt s1 = {1};          //a=1
6
struct sExt s2 = {1, {2}};     //a=1, b.b1=2
7
struct sExt s3 = {1, {2,3}, 4};//a=1, b.b1=2, b.b2=3, c=4
8
struct sExt s4 = {1, {},    4};//a=1,                 c=4

von MaNi (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Im Eingangspost steht nichts von "nur C".

Sollte es C++ sein, wäre ein Konstruktor für die Struct auch noch eine 
Möglichkeit.
1
struct blafusel
2
{ 
3
  int x;
4
  int y;
5
  int z;
6
  blafusel(): x(0), y(0), z(0){}
7
};

von MaNi (Gast)


Lesenswert?

A. S. schrieb:
> structs in structs werden mit nested {} initialisiert.

Ich würde designated Initializer bevorzugen. Falls irgendjemand die 
Reihenfolge der Struct umstellt fliegt man da sonst relativ schnell auf 
die Schnauze und hat auch viel Tipparbeit alle Initialisierungslisten 
umzustellen.
1
struct sExt s1 = {.a=1};

von MaWin (Gast)


Lesenswert?

DerEgon schrieb:
> typedef struct
> {
>   int x;
>   int y;
>   int z;
> } blafusel;
> blafusel meineVariable = { 0, 0, 0 };

Die Nullen kannst du dir sparen.
1
typedef struct
2
{
3
  int x;
4
  int y;
5
  int z;
6
} blafusel;
7
8
blafusel meineVariable = {};

von A. S. (Gast)


Lesenswert?

MaNi schrieb:
> Falls irgendjemand die
Namen
> der Struct umstellt fliegt man da sonst relativ schnell auf
> die Schnauze und hat auch viel Tipparbeit alle Initialisierungslisten
> umzustellen.

von N. M. (mani)


Lesenswert?

A. S. schrieb:
> Namen

Ne ich meinte schon die Reihenfolge.
Bei einer normalen initialisieren ist die Zuweisung der Werte von der 
Reihenfolge der Definition abhängig.
Bei Designated Initialisation nicht mehr.

von DPA (Gast)


Lesenswert?

MaWin schrieb:
> Die Nullen kannst du dir sparen.

Nein, das ist ub. Eine reicht aber, T x={0}; geht immer.
Irgendwann kommt noch c23 raus, dort darf man es dann endlich.

von MaWin (Gast)


Lesenswert?

DPA schrieb:
> Nein, das ist ub.

Unfug

von DPA (Gast)


Lesenswert?

MaWin schrieb:
> DPA schrieb:
>> Nein, das ist ub.
>
> Unfug

Ja, sorry, ich korrigiere mich, es ist nicht nur UB, es ist ein syntax 
Fehler. Hier ist der c11 draft:
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1516.pdf

Unter 6.7.9 Initialization, die formale Syntaxbeschreibung lässt es 
nicht zu.

Und zu c23 https://en.m.wikipedia.org/wiki/C2x
> Changes integrated into the latest working draft are: ... Zero initialization 
with {}

von A. S. (Gast)


Lesenswert?

N. M. schrieb:
> Bei Designated Initialisation nicht mehr.

Ja. Nur sind dann Umbenennungen aufwendig.

Da struct-member meist kurze Namen haben (nicht unique), geht 
suchen/ersetzen nicht.

Ich gruppiere praktisch nie um und erweitere nur am Ende. Umbenennungen 
dagegen jederzeit, wenn es zur Klarheit beiträgt.

Ich komme allerdings aus einer Zeit, wo designated nicht möglich war. 
Sonst hätte ich es vielleicht immer so gemacht.

von N. M. (mani)


Lesenswert?

A. S. schrieb:
> Ja. Nur sind dann Umbenennungen aufwendig.
> Da struct-member meist kurze Namen haben (nicht unique), geht
> suchen/ersetzen nicht.

Bessere IDEs bieten hier ein Rename Feature an die nicht nur den Namen 
sondern den Typ berücksichtigen beim suchen.
Sollte man dann trotzdem einen vergessen haben sieht man spätestens beim 
compileren wo der vergessene steckt, da es dann ja ein Compiler Error 
gibt.

A. S. schrieb:
> Ich komme allerdings aus einer Zeit, wo designated nicht möglich war.
> Sonst hätte ich es vielleicht immer so gemacht.

Das ist schon seit C99 drin 😄

Aber klar, jeder wie er möchte.

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

DPA schrieb:
> Ja, sorry, ich korrigiere mich, es ist nicht nur UB, es ist ein syntax
> Fehler.

Komisch, dass es mit GCC seit Jahrzehnten geht.

von (prx) A. K. (prx)


Lesenswert?

MaWin schrieb:
> Komisch, dass es mit GCC seit Jahrzehnten geht.

Nicht komisch, sondern für GCC in Standardeinstellung normal. GCC ist 
u.A. eine Art Testplattform für neue Entwicklungen der Sprache(n).

Probiers mal mit -pedantic.

von MaWin (Gast)


Lesenswert?

(prx) A. K. schrieb:
> Nicht komisch, sondern für GCC in Standardeinstellung normal.

Na dann ist ja alles gut und der Hinweis über UB war Quatsch.

von DPA (Gast)


Lesenswert?

MaWin schrieb:
> (prx) A. K. schrieb:
>> Nicht komisch, sondern für GCC in Standardeinstellung normal.
>
> Na dann ist ja alles gut und der Hinweis über UB war Quatsch.

Nein, das war kein quatsch. Leere initializer listen sind, momentan, 
schlicht kein gültiges C. Das GCC nicht abbricht, selbst wenn man ihm 
sagt sich an einen standard zu halten, ist eigentlich ein Fehler. Andere 
implementationen könnten/sollten da auch auf die schnauze fallen. Aber 
es ist eine kleine Erweiterung, und es verwirrt die user, wenn sie das 
nicht machen können, die denken dann, es wäre ein bug. Deshalb 
akzeptieren die meisten Compiler das halt trotzdem, es schadet ja 
keinem.

von MaWin (Gast)


Lesenswert?

DPA schrieb:
> Das GCC nicht abbricht, selbst wenn man ihm
> sagt sich an einen standard zu halten, ist eigentlich ein Fehler.

So ein Käse.
Ich gab hier lediglich einen praktischen Hinweis, der einem das Leben 
erleichtert.

Ich (und viele viele andere Entwickler) verwende diesen "Fehler" schon 
seit Jahrzehnten.

> es schadet ja keinem.

Na dann.

von DPA (Gast)


Lesenswert?

MaWin schrieb:
> Ich (und viele viele andere Entwickler) verwende diesen "Fehler" schon
> seit Jahrzehnten.

Ja, du (und viele andere), können halt kein C, wie man sieht.

von Rolf M. (rmagnus)


Lesenswert?

(prx) A. K. schrieb:
> In der Sprachsyntax von C ist etwas wie die Typedef-Namen eigentlich
> überhaupt nicht sauber möglich. Einer der Geburtsfehler der Syntax, denn
> ursprünglich gab es keine typedefs.
>
> Denn die üblichen Typnamen wie "int" sind Keywords, und das nicht ohne
> Grund.

Ja, weil sie in der Sprache fest eingebaut sind.

> Weshalb Typedef-Namen ebenfalls die Rolle von Keywords übernehmen
> müssen, aber natürlich nur so halb, andernfalls wären sie beispielsweise
> nicht als Strukt-Namen möglich.

Die Argumentation verstehe ich nicht. Eine Sprache, in der alle Typnamen 
Keywords sind, ist ziemlich limitiert.

> Ja nach syntaktischem Kontext werden Typedef-Namen vom Lexer als quasi-
> Keywords oder als normale Identifier betrachtet.

Was ist ein quasi-Keyword, und wo werden typedef-Namen so behandelt?

> Wer Sinn für formale Syntax hat, kriegt bei sowas die Krise.

Sieht für mich jetzt eigentlich nach etwas aus, das so ziemlich in jeder 
ernstzunehmenden Programmiersprache so ist.

Wilhelm M. schrieb:
> Im Eingangspost steht nichts von "nur C".

Da der TE aber von "typedef structs" spricht, ist eher davon auszugehen.

A. S. schrieb:
> N. M. schrieb:
>> Bei Designated Initialisation nicht mehr.
>
> Ja. Nur sind dann Umbenennungen aufwendig.
>
> Da struct-member meist kurze Namen haben (nicht unique), geht
> suchen/ersetzen nicht.

Dafür haben Editoren doch heute eine Funktion, die das semantikbasiert 
über den kompletten Code hinweg macht.

> Ich gruppiere praktisch nie um und erweitere nur am Ende. Umbenennungen
> dagegen jederzeit, wenn es zur Klarheit beiträgt.

Bei mir ist es gerade umgekehrt. Elemente einer struct nenne ich nur 
sehr selten um (höchstens mal, um einen Tippfehler zu korrigieren), aber 
dass mal zwischen zwei bestehenden Elementen ein neues hinzukommt statt 
nur am Ende, das kann schon mal vorkommen. Oder auch, dass ein nicht 
mehr benötigtes entfernt wird. Sowas nur deshalb drin (und dann 
ungenutzt) zu lassen, damit sich das Layout der Struktur nicht ändert, 
finde ich nicht sonderlich elegant.

> Ich komme allerdings aus einer Zeit, wo designated nicht möglich war.
> Sonst hätte ich es vielleicht immer so gemacht.

Ich komme auch aus dieser Zeit, aber ich hab halt dazugelernt. ;-)

MaWin schrieb:
> Ich (und viele viele andere Entwickler) verwende diesen "Fehler" schon
> seit Jahrzehnten.

Das macht es nicht automisch zu korrektem C.

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

Rolf M. schrieb:
> Das macht es nicht automisch zu korrektem C.

Leute, Leute.
Es war ein praktischer und gut gemeinter Hinweis.

Als Antwort kam dann, dass es UB sei. Was völliger Unsinn ist.
Und dann kam die Ausflucht, dass es Syntax Error sei. Was auch Unsinn 
ist, bei den meisten gängigen Compilern.

Soll der Threadersteller doch selbst entscheiden, ob er es nutzen will.
Falsche Behauptungen (UB) helfen ihm dabei sicher nicht.

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
>> Denn die üblichen Typnamen wie "int" sind Keywords, und das nicht ohne
>> Grund.
>
> Ja, weil sie in der Sprache fest eingebaut sind.

Keyword = reservierter Bezeichner, der eine prägende Rolle in der Syntax 
der Sprache spielt.

>> Ja nach syntaktischem Kontext werden Typedef-Namen vom Lexer als quasi-
>> Keywords oder als normale Identifier betrachtet.
>
> Was ist ein quasi-Keyword, und wo werden typedef-Namen so behandelt?

In der lexikalischen Analyse, also der Zerlegung des Quelltextes in 
syntaktische Elemente, wird zwischen reservierten Wörtern wie "int" und 
anderen Namen unterschieden. Der Lexer wirft also bei "int" das Token 
INT aus, bei normalen Namen aber NAME.

In der Syntax von C wird der Beginn einer Deklaration/Definition durch 
solche Keyword-Token wie "extern" oder "int" erkannt. Das war anno 
Sprachursprung syntaktisch sauber.

Mit Typedefs wurde es haarig, denn die haben ja syntaktisch exakt die 
gleiche Rolle wie obige Keywords, sind aber keine. Obendrein gibts sowas 
wie
  typedef struct s *s;    // incomplete
  struct s { s p; ... };  // completion
worin s zwei verschiedene lexikalische Rollen hat. In der zweiten Zeile 
muss der Lexer abhängig vom syntaktischen Kontext des Parsers den 
gleichen Namen s wahlweise in TYPENAME oder NAME auflösen:
  STRUCT //expect=name// NAME '{' //expect=type// TYPENAME NAME ';' ...
Man muss also im Parser die Arbeitsweise des Lexers umschalten, abhängig 
vom syntaktischen Kontext. Und das ist eine zwar machbare, aber 
konzeptionell unsaubere Sache.

Obacht Falle: Ich beziehe mich hier nicht auf verschiedene Namespaces 
innerhalb normaler Identifier, wie etwa dem getrennten Namespace der 
Namen von structs. Das ist ein völlig anderes Thema.

> Sieht für mich jetzt eigentlich nach etwas aus, das so ziemlich in jeder
> ernstzunehmenden Programmiersprache so ist.

Keineswegs. Bei einer Syntax wie
   TYPE t: ...;
   VAR i: t;
sind nur TYPE und VAR Keywords. Der Typname t ist ein ganz gewöhnlicher 
Name, ohne jedwede Sonderrolle.

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

MaWin schrieb:
> Als Antwort kam dann, dass es UB sei. Was völliger Unsinn ist.
> Und dann kam die Ausflucht, dass es Syntax Error sei. Was auch Unsinn
> ist, bei den meisten gängigen Compilern.

Kein unsinn, sondern tatsache. Was nicht dem Standard entspricht, ist 
kein C, was der Compiler schluckt ist da irrelevant.

von Rolf M. (rmagnus)


Lesenswert?

(prx) A. K. schrieb:
> Keyword = reservierter Bezeichner, der eine prägende Rolle in der Syntax
> der Sprache spielt.

Ja, genau deshalb sind die einebauten Typen Schlüsselwörter.

>> Was ist ein quasi-Keyword, und wo werden typedef-Namen so behandelt?
>
> In der lexikalischen Analyse, also der Zerlegung des Quelltextes in
> syntaktische Elemente, wird zwischen reservierten Wörtern wie "int" und
> anderen Namen unterschieden. Der Lexer wirft also bei "int" das Token
> INT aus, bei normalen Namen aber NAME.

Das ist mir bewusst, nennt man in der Regel aber eher Identifier. Der 
ist aber kein "pseudo-Keyword".
Da wäre eher noch die umgekehrte Betrachtungsweise angebracht, dass 
Keywords eine Art pseudo-Idendifier sind, weil sie auch der 
lexikalischen Regel für Identifier entsprechen würden, aber eine 
besondere Rolle spielen.

> In der Syntax von C wird der Beginn einer Deklaration/Definition durch
> solche Keyword-Token wie "extern" oder "int" erkannt. Das war anno
> Sprachursprung syntaktisch sauber.
>
> Mit Typedefs wurde es haarig, denn die haben ja syntaktisch exakt die
> gleiche Rolle wie obige Keywords, sind aber keine.

Syntaktisch haben sie die Rolle eines type-specifier. Die Syntax von C 
ist im Standard definiert und sieht an der Stelle so aus:
1
type-specifier:
2
    void
3
    char
4
    short
5
    int
6
    long
7
    float
8
    double
9
    signed
10
    unsigned
11
    _Bool
12
    _Complex
13
    struct-or-union-specifier
14
    enum-specifier
15
    typedef-name
Und ein typedef-name ist dann schließlich ein Identifier.

> Obendrein gibts sowas
> wie
>   typedef struct s *s;    // incomplete
>   struct s { s p; ... };  // completion
> worin s zwei verschiedene lexikalische Rollen hat. In der zweiten Zeile
> muss der Lexer abhängig vom syntaktischen Kontext des Parsers den
> gleichen Namen s wahlweise in TYPENAME oder NAME auflösen:

Nein, der Lexer löst das einfach als Identifier mit Text "s" auf, und 
das gibt er an den nachgeschalteten Parser weiter. Der erkennt dann 
anhand der passendend Parser-Regel, auf was sich der Identifier an der 
jeweiligen Stelle beziehen muss.

>> Sieht für mich jetzt eigentlich nach etwas aus, das so ziemlich in jeder
>> ernstzunehmenden Programmiersprache so ist.
>
> Keineswegs. Bei einer Syntax wie
>    TYPE t: ...;
>    VAR i: t;
> sind nur TYPE und VAR Keywords. Der Typname t ist ein ganz gewöhnlicher
> Name, ohne jedwede Sonderrolle.

Und eingebaute Typen hat diese Sprache dann nicht?

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Nein, der Lexer löst das einfach als Identifier mit Text "s" auf, und
> das gibt er an den nachgeschalteten Parser weiter.

Ich erinnere mich sowohl an die von mir beschriebene Variante (uralt), 
finde aber auch einen aktuellen Lexer, der diesen Teil der 
Kontextabhängigkeit selbst erledigt.

Nach "notype" suchen. Liefert entweder C_NAME oder C_TYPENAME:
https://github.com/IanHarvey/pcc/blob/master/cc/ccom/scan.l

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Und eingebaute Typen hat diese Sprache dann nicht?

Deren Namen sind keine Keywords / reserved Words, sondern lediglich 
vordefinierte normale Identifier. In Pascal ist VAR reserviert, INTEGER 
aber ein gewöhnlicher Identifier.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

(prx) A. K. schrieb:
> Ich erinnere mich sowohl an die von mir beschriebene Variante (uralt),
> finde aber auch einen aktuellen Lexer, der diesen Teil der
> Kontextabhängigkeit selbst erledigt.

Hat das denn einen speziellen Vorteil? Es scheint mir unnötig 
umständlich, in den Lexer eine Kontextabhängigkeit einzubauen, die man 
eigentlich nicht unbedingt bräuchte.

(prx) A. K. schrieb:
> Rolf M. schrieb:
>> Und eingebaute Typen hat diese Sprache dann nicht?
>
> Deren Namen sind keine Keywords / reserved Words, sondern lediglich
> vordefinierte normale Identifier. In Pascal ist VAR reserviert, INTEGER
> aber ein gewöhnlicher Identifier.

Ok, das kann man machen. Könnte man dann eine Variable oder Funktion 
definieren, die INTEGER heißt?

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Ok, das kann man machen. Könnte man dann eine Variable oder Funktion
> definieren, die INTEGER heißt?

Syntaktisch wäre das kein Problem. Nachschlagen darfst du selbst. ;-)

von Yalu X. (yalu) (Moderator)


Lesenswert?

Am deutlichsten wird die Problematik von typedef in folgendem Beispiel:
1
x * y;

Im Kontext
1
int x, y;

ist es ein Produkt, dessen Ergebnis nicht weiter verwendet wird.

Im Kontext
1
typedef int x;

hingegen wird damit y als Zeiger auf int deklariert.

Das sind zwei völlig verschiedene Dinge, die weder mittels regulärer
Ausdrücke (für den Lexer) noch mittels EBNF (für den Parser)
unterschieden werden können. Man benötigt dafür also zusätzliche
Verrenkungen, die wahlweise im Lexer oder im Parser implementiert
werden. Elegant ist das aber in beiden Fällen nicht.

von Yalu X. (yalu) (Moderator)


Lesenswert?

DPA schrieb:
> Leere initializer listen sind, momentan, schlicht kein gültiges C.

So ist es.

> Das GCC nicht abbricht, selbst wenn man ihm sagt sich an einen
> standard zu halten, ist eigentlich ein Fehler.

Mit -pedantic-errors weist man den Compiler an, sich strikt an den
Standard zu halten. Dann gibt er erwartungsgemäß auch eine entsprechende
Fehlermeldung aus:
1
test.c:1:23: error: ISO C forbids empty initializer braces [-Wpedantic]
2
    1 | struct { int x; } s = {};
3
      |                       ^

von (prx) A. K. (prx)


Lesenswert?

Yalu X. schrieb:
> Das sind zwei völlig verschiedene Dinge, die weder mittels regulärer
> Ausdrücke (für den Lexer) noch mittels EBNF (für den Parser)
> unterschieden werden können.

Dafür alleine reicht die Symboltabelle. Ergibt die im passenden Kontext, 
dass "y" ein Typedef ist, gibts C_TYPENAME, sonst C_NAME. Siehe oben 
verlinkten Lexer-Code.

Interessant wird es erst dann, wenn das y auch an Stellen vorkommt, wo 
es kein Typname sein kann. Meine Struct war nur ein Beispiel.
1
typedef int y;
2
3
int f(void)
4
{
5
        y y = y;    // Lex: C_TYPENAME C_NAME '=' C_NAME
6
        return y;
7
}
In C++ wirds über die Cast-Syntax noch etwas schöner:
1
typedef int y, z;
2
int k;
3
4
int f(int i)
5
{
6
        y x = y(i); // OK, ist ein Cast nach Typ y
7
        z z = z(i); // NOK, z() müsste eine Funktion sein
8
        y(k);       // wird hier k definiert, oder ist das ein nutzloser Cast?
9
        return x;
10
}

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

(prx) A. K. schrieb:
> Yalu X. schrieb:
>> Das sind zwei völlig verschiedene Dinge, die weder mittels regulärer
>> Ausdrücke (für den Lexer) noch mittels EBNF (für den Parser)
>> unterschieden werden können.
>
> Dafür alleine reicht die Symboltabelle.

Ja, klar, nur hat in einer idealen Welt weder die lexikalische noch die
syntaktische Analyse etwas mit Symboltabellen am Hut. Dass das in der
realen Welt dennoch der Fall ist, liegt eben an dem etwas unglücklich
nachgerüsteten typedef-Konstrukt.

> y y = y;

Wäre dies das alleinige Problem, könnte man im Lexer und Parser auf den
Zugriff auf die Symboltabelle verzichten und stattdessen den Look-Ahead
des Parsers von 1 auf 2 erhöhen. Bei zwei aufeinanderfolgenden
Identifiern an dieser Stelle ist der erste IMHO immer ein Typname
(zumindest fällt mir spontan kein Gegenbeispiel ein).

Damit wäre der Lexer ein reiner Lexer, der Parser ein reiner Parser und
die Welt wieder etwas idealer :)

Aber die Mehrdeutigkeit in meinem obigen Beispiel
1
x * y;

kann auch mit einem größeren (fixen) Look-Ahead nicht aufgelöst werden,
weswegen hier tatsächlich der Zugriff auf die Symboltabelle benötigt
wird.

von Rolf M. (rmagnus)


Lesenswert?

Yalu X. schrieb:
> Am deutlichsten wird die Problematik von typedef in folgendem Beispiel:

Ok, ich sehe das Problem. Das liegt aber an sich weniger an typedef, 
sondern eher an der Mehrdeutigkeit von x * y. Das ist besonders 
ärgerlich, weil es als Multiplikation eigentlich keinen Sinn gibt, das 
so für sich hinzuschreiben, es aber trotzdem durch die Sprache erlaubt 
ist. Als Teil eines
1
z = x * y;
wäre es dann wieder eindeutig.

(prx) A. K. schrieb:
> y(k);       // wird hier k definiert, oder ist das ein nutzloser
> Cast?

In C++ gibt's die Regel: Wenn es eine Deklaration sein könte, dann ist 
es auch eine. Es wird also eine Variable namenes k vom Typ y definiert.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Das Hauptproblem war die Grundentscheidung, Datentypen, Speicherklassen 
etc zu Keywords zu machen und damit Deklarationen anzufangen. Damit lag 
nicht nur dieses Kind im Brunnen.

: Bearbeitet durch User
von Lars R. (larsr)


Lesenswert?

Mal ganz grundsätzlich gefragt: Wieso wird in diesem einfachen Beispiel 
überhaupt typedef genutzt?

Ich nutze diese Dinger eigentlich nur für Funktionszeiger, da ist mir 
die Syntax sonst zu umständlich. Aber bei einem struct oder enum 
schreibe ich lieber "struct x" bzw. "enum y"...

Man kann sich das Leben unnötigerweise beliebig kompliziert machen.

von Rolf M. (rmagnus)


Lesenswert?

Du meinst, weil es an dem Punkt noch eindeutig war?
Vereinfacht:

KEYWORD * IDENTIFIER;    = Zeigerdefinition
IDENTIFIER * IDENTIFIER; = Multiplikation

Erst durch typedef kann auch IDENTIFIER * IDENTIFIER eine 
Zeigerdefinition sein.

Dann ist mir klar geworden, was du gemeint hast, und ich stimme zu.
Ich finde die Diskussion sehr interessant, da ich selbst auch an der 
Entwicklung der ersten Version eines (wenn auch deutlich einfacheren) 
Sprachstandards teilgenommen habe, und da gab's natürlich auch einige 
Diskussionen, wie man Sachen so definiert, dass sie einem später so 
wenig wie möglich auf die Füße fallen. Ich bin schon gespannt, was uns 
bei der Fortführung erwartet. 🙂

von (prx) A. K. (prx)


Lesenswert?

Lars R. schrieb:
> Mal ganz grundsätzlich gefragt: Wieso wird in diesem einfachen Beispiel
> überhaupt typedef genutzt?

Die letzten Beiträge? Das sind bewusst minimierte Beispiele für Syntax, 
kein produktiver Code.

> Ich nutze diese Dinger eigentlich nur für Funktionszeiger

Und sonst nur "struct s" und "union u"?

von Rolf M. (rmagnus)


Lesenswert?

(prx) A. K. schrieb:
> Und sonst nur "struct s" und "union u"?

Mache ich in C auch so. Warum soll ich hinter einem typedef verstecken, 
dass es eine struct ist? Gibt es einen anderen Grund als Tippfaulheit?

von Lars R. (larsr)


Lesenswert?

(prx) A. K. schrieb:
> Und sonst nur "struct s" und "union u"?

Ja. Gefällt mir optisch schon deutlich besser und damit man sofort 
sieht, es ist kein primitiver Datentyp und bei einer Zuweisung wird's 
teuer (wegen der Kopieraktion).

(prx) A. K. schrieb:
> Das Hauptproblem war die Grundentscheidung, Datentypen, Speicherklassen
> etc zu Keywords zu machen und damit Deklarationen anzufangen. Damit lag
> nicht nur dieses Kind im Brunnen.

Das ist gerade das, was (aus meiner Sicht) C so elegant macht.

Andere Sprachen fügen dafür Keywords wie "var"/"let"/"mut" (oder gar 
"function" oder "fn") ein oder basteln sich Konstruktionen mit 
Doppelpunkten. Am schlimmsten finde ich aber, dass moderne Sprachen (wie 
Rust) den Datentyp erst nach dem Identifier haben "let xy : int;" (oder 
so etwas in der Art). Das ist doch scheußlich!

An diesen ganzen Dingen erkenne ich einfach nur, dass man es sich schon 
bei der Spezifikation der Sprache leicht machen wollte den Compiler zu 
implementieren.

Noch schlimmer ist als Zuweisung ":=" zu verwenden und als Vergleich "=" 
- das, was man viel häufiger braucht (nämlich die Zuweisung), sollte 
stets kürzer sein.

Diese ganzen modernen Sprachen kopieren Teile der Syntax der 
"C-Sprachen", bauen aber gerade da, wo C wirklich Tipparbeit spart, neue 
Hässlichkeiten ein.

Microsoft hat mit C# beispielsweise etwas geschaffen, was viel eher nach 
"richtigem C" aussieht als beispielsweise Rust.

von MaWin (Gast)


Lesenswert?

Lars R. schrieb:
> dass moderne Sprachen (wie
> Rust) den Datentyp erst nach dem Identifier haben "let xy : int;" (oder
> so etwas in der Art). Das ist doch scheußlich!

Das ist einfach nur Gewöhnungs und Geschmacksacke. Ich finde die 
name:typ Schreibweise mittlerweile deutlich besser.

> An diesen ganzen Dingen erkenne ich einfach nur, dass man es sich schon
> bei der Spezifikation der Sprache leicht machen wollte den Compiler zu
> implementieren.

Warum wäre das schlimm?
Ich finde es außerdem gut, wenn Syntax eindeutiger wird. Nicht nur für 
den Compiler.
Rust hat ganz bewusst mit vielen Mehrdeutigkeiten von C/C++ aufgeräumt.

von (prx) A. K. (prx)


Lesenswert?

Lars R. schrieb:
> Andere Sprachen fügen dafür Keywords wie "var"/"let"/"mut" (oder gar
> "function" oder "fn") ein oder basteln sich Konstruktionen mit
> Doppelpunkten.

Ja, und sie machen es richtig.

Ich gebe hier nicht den c-hater. Ich verwende C seit 4 Jahrzehnten, ich 
kenne es recht gut, von aussen und innen. Aber ich fand den 
syntaktischen Ansatz der Wirth'schen Sprachen seit damals besser. Ich 
verwendete früher auch C++ häufig, obwohl mir bereits bei der ersten 
Lektüre des Stroustrup an manchen Stellen Zweifel aufkamen, ob das 
wirklich so hätte aussehen müssen.

Ich hatte mir auch manchen C Nachfolger angesehen, und habe erst bei 
Rust den Eindruck, dass man sich ernsthaft vom überkommenen Erbe trennt. 
Immer wieder fand ich sonst gute Ideen verknüpft mit alten Fehlern (etwa 
in D).

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Lars R. schrieb:
> An diesen ganzen Dingen erkenne ich einfach nur, dass man es sich schon
> bei der Spezifikation der Sprache leicht machen wollte den Compiler zu
> implementieren.

Ja. Aber nicht an der Stelle. Weniger Keywords vereinfachen die Sprache. 
Wenn also Typen keine Keywords sind, wenn auch Attribute wie const, 
volatile, static, extern ... so strukturiert werden, dass man nicht für 
jedes davon ein Keyword benötigt, dann kann eine Sprache wirklich 
einfacher werden. Und leichter erweiterbar.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Lars R. schrieb:
> Das ist doch scheußlich!

Bloss anders. Wer auf C geprägt ist, wie die kleine Gans auf die Eltern, 
der kann natürlich damit erst einmal ein Problem haben, wenn etwas 
auftaucht, das anders aussieht. Ich hatte aber nicht mit C angefangen, 
kannte davor schon Sprachen ohne syntaktische Gemeinsamkeit mit C.

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Gibt es einen anderen Grund als Tippfaulheit?

Es müssen wohl einige gesehen haben, dass es sich lohnt. Was man schon 
daran erkennt, dass es schon vor der Veröffentlichung der ersten 
K&R-Auflage nachträglich eingebaut wurde.

Verwendet man den gleichen Datentyp mit der gleichen Bedeutung an 
verschiedenen Stellen (z.B. "Helligkeit"), ist ein Typedef dafür sehr 
sinnvoll. Baut man komplexere Typen aus Arrays und Pointer auf, wird es 
ohne Typedefs schnell unlesbar. Nicht nur, wenn auch Funktionen dabei 
sind, wie oben erwähnt.

von Rolf M. (rmagnus)


Lesenswert?

(prx) A. K. schrieb:
> Rolf M. schrieb:
>> Gibt es einen anderen Grund als Tippfaulheit?
>
> Es müssen wohl einige gesehen haben, dass es sich lohnt. Was man schon
> daran erkennt, dass es schon vor der Veröffentlichung der ersten
> K&R-Auflage nachträglich eingebaut wurde.

Ich meine nicht typedef allgemein, sondern speziell die, die nur dazu 
genutzt werden, um be struct-Typen das "struct" weglassen zu können.
1
typedef struct my_struct my_struct;

(prx) A. K. schrieb:
> Verwendet man den gleichen Datentyp mit der gleichen Bedeutung an
> verschiedenen Stellen (z.B. "Helligkeit"), ist ein Typedef dafür sehr
> sinnvoll.

Wobei an der Stelle von Nachteil ist, dass typedef eben keinen neuen Typ 
definiert, d.h. der Compiler kann nicht überprüfen, ob man dort, wo eine 
Helligkeit erwartet wird, auch tatsächlich eine solche angegeben hat 
oder vielleicht doch stattdessen eine Lautstärke.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Wobei an der Stelle von Nachteil ist, dass typedef eben keinen neuen Typ
> definiert

Ja. Ist aber ein völlig anderes Thema. Ada hat deshalb beides - 
Typableitung mit und ohne Verträglichkeit des abgeleiten Typs mit dem 
Original.

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Ich meine nicht typedef allgemein, sondern speziell die, die nur dazu
> genutzt werden, um be struct-Typen das "struct" weglassen zu können.

Das ergibt sich als Nebeneffekt eines allgemeinen Typedefs von alleine.

von Rolf M. (rmagnus)


Lesenswert?

Hab ich mich so ungeschickt ausgedrückt? Ich meine nicht die Existenz 
des Typedefs, sondern speziell seine Nutzung für den oben beschriebenen 
Anwendungsfall. Ich hatte mich dabei auf diese Aussage bezogen:

Lars R. schrieb:
> Aber bei einem struct oder enum
> schreibe ich lieber "struct x" bzw. "enum y"...

Also welchen Voteil, außer dass ich bei der Verwendung an ein paar 
Stellen das Wörtchen "struct" nicht tippen muss, hat es, wenn ich als 
Programmier schreibe:
1
typedef struct Vektor_3d
2
{
3
    float x;
4
    float y;
5
    float z;
6
}  Vektor_3d;

an Stelle von:
1
struct Vektor_3d
2
{
3
    float x;
4
    float y;
5
    float z;
6
};

von udok (Gast)


Lesenswert?

Das erste Beispiel kannst du abkürzen zu
1
typedef struct {
2
    float x;
3
    float y;
4
    float z;
5
}  Vektor_3d;

Der Vorteil ist die flüssigere Lesbarkeit und das weniger an Tipparbeit, 
zumindest bei so fundamentalen Typen wie Vector_3d, die 100'erte Male 
vorkommen. C++ macht es ja nicht ohne Grund genauso.  Da musst du auch 
nicht class Vector_3d schreiben.  Zugegeben, man kann auch ohne dieses 
Feature gut programmieren.

Und das ein typedef nur ein Alias ist, ist aus C Sicht heraus sinnvoll.
Bei fundamentalen integer typen gilt ja automatische Typumwandlung, da 
macht
es keinen Sinn für "typedef int my_int_t" eine verwirrende Ausnahme zu 
machen.
Will man einen echten eigenen Typ haben, dann macht man einfach ein 
"typedef struct { int i; } my_int_t;", und fertig.  typedef ist ein 
nicht optimal gewählter Name, besser wäre typealias oder typename.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Rolf M. schrieb:
> (prx) A. K. schrieb:
>> Und sonst nur "struct s" und "union u"?
>
> Mache ich in C auch so.

Ich auch.

> Warum soll ich hinter einem typedef verstecken, dass es eine struct
> ist?

Diese Mode kam IMHO erst mit der Einführung von C++ so richtig auf, wo
man das "struct", "union" und "enum" bei Typnamen generell weglassen
kann, auch ohne typedef. Manche, die sich daran gewöhnt haben, möchten
das auch in C so haben und typedeffen deswegen diese Schlüsselwörter
weg.

> Gibt es einen anderen Grund als Tippfaulheit?

IMHO nur sehr wenige.

Bei der Definition eines API kann es manchmal sinnvoll sein, bei einem
plattformspezifischen Datentyp, der je nach Plattform ein int oder ein
struct sein kann, diesen Unterschied mittels typedef vor dem Benutzer zu
verbergen. Dieses Ziel kann man allerdings auch dadurch erreichen, dass
man auch das int in ein Dummy-struct verpackt, so dass der Benutzer
unabhängig vom tatsächlichen Typ immer ein struct und damit ebenfalls
keinen Unterschied sieht.

von (prx) A. K. (prx)


Lesenswert?

Es gibt andererseits eine Stelle, wo struct/union-Namen statt 
typedef-Namen wichtig sind, und auch das hat mit dusseliger Syntax zu 
tun. Wenn sie innerhalb davon verwendet werden:
1
typedef struct S
2
{
3
  struct S *next;
4
  ...
5
} S;
Stünde der Typname vorne, wie es sich gehört, wäre das kein Thema, weil 
dann der Typ innerhalb der Struct-Deklaration schon bekannt ist.
1
type S: struct { ... };
C++ hat das repariert, indem der Struct-Name zum Typedef-Name wurde. In 
C hätte man eine heilige Kuh schlachten müssen.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Yalu X. schrieb:
> Diese Mode kam IMHO erst mit der Einführung von C++ so richtig auf

Hmm. Ich hatte das von Anfang an so gehalten, lange vor C++, und war 
damit nicht alleine.

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

(prx) A. K. schrieb:

> C hätte man eine heilige Kuh schlachten müssen.

Hat man ja getan. Fast von Anfang an. Nennt sich Vorwärtsdeklaration und 
bezieht sich tatsächlich auf den (sonst irgendwie völlig nutzlosen) 
Namen der typedef-Strukturdeklaration.

typedef foo * fooptr_t;
typedef struct foo {
  //whatever else
  fooptr_t PointerToAnyInstanceOfStructFoo;
}foo_t;

C ist Dreck, völliger struktureller Dreck vom tiefsten Grund der 
Sprachdefinition her. Das muss man einfach immer berücksichtigen, dann 
fällt es viel leichter, den ganzen stupiden Wahnsinn mit einem 
gelassenen Grunzen zu akzeptieren.

von Yalu X. (yalu) (Moderator)


Lesenswert?

c-hater schrieb:
> C ist Dreck,

Diese Aussage wiederholst du (sinngemäß) im Mittel einmal pro Woche, und
das schon seit Jahren. Dabei ist diese Information bereits vollständig
in deinem Nick enthalten und deswegen jeder deiner Texte redundant.

Ist das eigentlich dein kompletter Wissensstand, was Computer und
Programmierung betrifft, oder weißt du auch noch etwas anderes?

von c-hater (Gast)


Lesenswert?

Yalu X. schrieb:
> c-hater schrieb:
>> C ist Dreck,
>
> Diese Aussage wiederholst du (sinngemäß) im Mittel einmal pro Woche, und
> das schon seit Jahren. Dabei ist diese Information bereits vollständig
> in deinem Nick enthalten und deswegen jeder deiner Texte redundant.
>
> Ist das eigentlich dein kompletter Wissensstand, was Computer und
> Programmierung betrifft, oder weißt du auch noch etwas anderes?

Du weißt sehr genau, dass ich auch noch anderes weiß. Das ist aber 
garnicht dein eigentliches Problem, du könntest gut damit umgehen, dass 
ich mehr weiß, als dass C als Sprache grundsätzlich ziemlich Scheiße 
ist.

Dein Problem ist: dich kotzt es maximal an, dass die von dir präferierte 
Sprache recht regelmäßig als eigentlich völlig ziemlich untauglich 
entblößt wird. Das geht bei dir an die Substanz, denn du kannst ja nicht 
wirklich etwas anderes...

Das vermittelt dir ein gewisses Minderwertigkeitsgefühl. Tja schlecht 
für dich. Aber Fakten können auf Befindlichkeiten selbst von Moderatoren 
mit Lösch-Macht leider keine Rücksicht nehmen...

von Yalu X. (yalu) (Moderator)


Lesenswert?

c-hater schrieb:
> Dein Problem ist: dich kotzt es maximal an, dass die von dir präferierte
> Sprache recht regelmäßig als eigentlich völlig ziemlich untauglich
> entblößt wird.

Häh? Hier geht es doch um C, wieso fängst du jetzt plötzlich mit Python
und Haskell an (von denen du vermutlich genauso wenig Ahnung wie von C
hast)?

von Rolf M. (rmagnus)


Lesenswert?

c-hater schrieb:
> Du weißt sehr genau, dass ich auch noch anderes weiß. Das ist aber
> garnicht dein eigentliches Problem, du könntest gut damit umgehen, dass
> ich mehr weiß, als dass C als Sprache grundsätzlich ziemlich Scheiße
> ist.

Ich kann dir sagen, was mein Problem damit ist: Dass du offenbar 
regelrecht besessen von der Sprache bist. Anders kann ich mir nicht 
erklären, warum du trotz dieser Meinung den anscheinend 
unwiderstehlichen Drang verspürst, zu jedem, aber auch absolut jedem 
Thread über das Theman deinen Senf dazugeben zu müssen.
Es gibt auch Sprachen, die ich nicht mag. Dann ziehe ich aber nicht los 
und streue in jede einzelne Diskussion über diese Sprache ein, dass die 
doch totaler Mist sei. Sondern ich halte dazu einfach die Klappe.

von Wilhelm M. (wimalopaan)


Lesenswert?

(prx) A. K. schrieb:
> C++ hat das repariert, indem der Struct-Name zum Typedef-Name wurde.

Naja, C++ hat da nichts repariert. Sondern es ist in C++ eine absolute 
Notwendigkeit.
Das liegt daran, das man in C im engeren Sinn keine UDT definieren kann. 
C structs sind zwar "struktierierte Datentypen" im Sinne der Sprache C, 
aber sie sind eben keine (abstrakte) Datentypen im Sinne der Informatik. 
Zwar kann man Operationen auf Datentypen in C mit Funktionen realisieren 
(das geht für die strukturiereten DT wie für die primitiven DT), 
andererseits kann man die Operatoren (+, -, ...) der Sprache zwar auf 
die primitiven DT anwenden, aber eben nicht auf die strukturierten 
Datentypen.

Dies steht aber im Widerspruch zur generischen Programmierung, was ja 
C++ in Form von Templates unterstützt. Hier ist es eben absolut 
notwendig, dass UDT und primitive DT gleich mächtig sind, will heißen, 
dass man Operationen in Form von Funktionen wie auch in Form von 
Operatoren auf beides anwenden kann (hiermit ist nicht Uniform function 
call syntax gemeint, sondern es geht um die Idempotenz von UDT und 
primitiven DT).

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wilhelm M. schrieb:
> (prx) A. K. schrieb:
>> C++ hat das repariert, indem der Struct-Name zum Typedef-Name wurde.
>
> Naja, C++ hat da nichts repariert. Sondern es ist in C++ eine absolute
> Notwendigkeit.

Das verstehe ich nicht. Es geht doch um das Schlüsselwort "struct", was
in C dem Typnamen einer Struktur ohne Alias vorangestellt werden muss
und in C++ weggelassen werden kann?

Falls ja: Welche Dinge wären in C++ nicht möglich, wenn dort wie in C
das "struct" verpflichtend wäre?

von Mombert H. (mh_mh)


Lesenswert?

Yalu X. schrieb:
> Falls ja: Welche Dinge wären in C++ nicht möglich, wenn dort wie in C
> das "struct" verpflichtend wäre?
Die Frage ist, wo es genau stehen müsste.

von Rolf M. (rmagnus)


Lesenswert?

Mombert H. schrieb:
> Yalu X. schrieb:
>> Falls ja: Welche Dinge wären in C++ nicht möglich, wenn dort wie in C
>> das "struct" verpflichtend wäre?
> Die Frage ist, wo es genau stehen müsste.

Vor dem Namen des Typs, wie in C auch. Ich verstehe auch nicht ganz, was 
das mit Operator-Überladung zu tun haben soll.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Rolf M. schrieb:
> Ich verstehe auch nicht ganz, was das mit Operator-Überladung zu tun
> haben soll.

Ich auch nicht und auch generell nicht, wie das Ganze mit abstrakten
Datentypen zusammenhängt. Ist ein Datentyp denn weniger abstrakt, wenn
man ein "struct" davor schreibt?

Ich sehe höchstens einen ästhetischen Vorteil, da ohne das "struct"
Struktur- und Klassenamen gleich aussehen wie die Namen primitiver
Datentypen.

Aber sicher wird uns Wilhelm gleich erleuchten :)

von (prx) A. K. (prx)


Lesenswert?

Yalu X. schrieb:
> Ist ein Datentyp denn weniger abstrakt, wenn
> man ein "struct" davor schreibt?

Schon etwas.
1
   typedef int A;
2
   struct B { B(int); ... }
3
4
   ... A(1) ...
5
   ... B(1) ...

> Struktur- und Klassenamen gleich aussehen wie die Namen primitiver
> Datentypen.

Eben.

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

(prx) A. K. schrieb:
> Yalu X. schrieb:
>> Ist ein Datentyp denn weniger abstrakt, wenn
>> man ein "struct" davor schreibt?
>
> Schon etwas.
> ...
>
>   ... A(1) ...
>   ... B(1) ...

So ist es natürlich schöner anzusehen, das ist aber auch alles.

Laut Wilhelm scheint da mehr dahinterzustecken:

Wilhelm M. schrieb:
> Sondern es ist in C++ eine absolute Notwendigkeit.

Ich habe nur keine Idee, was das sein könnte, deswegen meine Frage oben.

: Bearbeitet durch Moderator
von (prx) A. K. (prx)


Lesenswert?

Yalu X. schrieb:
> So ist es natürlich schöner anzusehen, das ist aber auch alles.

Das Stichwort hattest du selbst genannt: Abstraktion. Das war ein 
wesentliches Ziel von C++. Es sollte kein "C mit Klassen" sein, sondern 
bei der Nutzung eines Objektes im Code von der konkreten Implementierung 
eines Objektes wegführen. Mit struct vorneweg passt das nicht.

von Lars R. (larsr)


Lesenswert?

(prx) A. K. schrieb:
> Mit struct vorneweg passt das nicht.

"struct" und "class" sind sowieso praktisch identisch in C++. Es gibt 
eine einzige Feinheit: "struct" fängt gleich mit "public:" an, "class" 
hingegen mit "private:". "struct xy { private: T ..." ist also völlig 
gleichbedeutend zu "class xy { T ..."

von Mombert H. (mh_mh)


Lesenswert?

Rolf M. schrieb:
> Mombert H. schrieb:
>> Yalu X. schrieb:
>>> Falls ja: Welche Dinge wären in C++ nicht möglich, wenn dort wie in C
>>> das "struct" verpflichtend wäre?
>> Die Frage ist, wo es genau stehen müsste.
>
> Vor dem Namen des Typs, wie in C auch. Ich verstehe auch nicht ganz, was
> das mit Operator-Überladung zu tun haben soll.
1
template <typename T>
2
auto add(T a, T b) -> T {
3
  return a + b;
4
};
5
6
struct S {
7
  int v;
8
};
9
auto operator+(S a, S b) -> S {
10
  return S{a.v + b.v};
11
}
12
13
void foo() {
14
  int a = 1;
15
  int b = 42;
16
  int c = add(a, b);
17
18
  S sa = {1};
19
  S sb = {42};
20
  S sc = add(sa, sb);
21
}
Wo soll jetzt überall nen struct stehen müssen/dürfen?

von Rolf M. (rmagnus)


Lesenswert?

Mombert H. schrieb:
> Wo soll jetzt überall nen struct stehen müssen/dürfen?

Na eben wie in C, immer vor dem Namen der Struct, also im Beispiel vor 
jedem S. So funktioniert es ja in C. Der Typ heißt dort eben nicht "S", 
sondern "struct S", also:
1
template <typename T>
2
auto add(T a, T b) -> T {
3
  return a + b;
4
};
5
6
struct S {
7
  int v;
8
};
9
10
auto operator+(struct S a, struct S b) -> struct S {
11
  return struct S{a.v + b.v};
12
}
13
14
void foo() {
15
  int a = 1;
16
  int b = 42;
17
  int c = add(a, b);
18
  struct S sa = {1};
19
  struct S sb = {42};
20
  struct S sc = add(sa, sb);
21
}
Ich sehe da jetzt kein Problem mit Operatoren, Funktionen oder 
Templates.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Wilhelm M. schrieb:
> (prx) A. K. schrieb:
>> C++ hat das repariert, indem der Struct-Name zum Typedef-Name wurde.

Der Begriff "typedef-Name" in diesem Satz ist nicht definiert, es ist 
wohl
der typ-alias gemeint.

> Naja, C++ hat da nichts repariert. Sondern es ist in C++ eine absolute
> Notwendigkeit.
> Das liegt daran, das man in C im engeren Sinn keine UDT definieren kann.
> C structs sind zwar "struktierierte Datentypen" im Sinne der Sprache C,
> aber sie sind eben keine (abstrakte) Datentypen im Sinne der Informatik.
> Zwar kann man Operationen auf Datentypen in C mit Funktionen realisieren
> (das geht für die strukturiereten DT wie für die primitiven DT),
> andererseits kann man die Operatoren (+, -, ...) der Sprache zwar auf
> die primitiven DT anwenden, aber eben nicht auf die strukturierten
> Datentypen.

In der Informatik bestehen Datentypen aus Werten und anwendbaren 
Operatoren.
In C haben wir eine Unsymmetrie zwischen strukturierten Datentypen und
primitiven DT, weil man die eingebauten Operatoren nicht für die
strukturierten Datentypen definieren kann. Ergo: strukturierte DT und 
primitive
DT sind nicht gleich mächtig.

> Dies steht aber im Widerspruch zur generischen Programmierung, was ja
> C++ in Form von Templates unterstützt. Hier ist es eben absolut
> notwendig, dass UDT und primitive DT gleich mächtig sind, will heißen,
> dass man Operationen in Form von Funktionen wie auch in Form von
> Operatoren auf beides anwenden kann (hiermit ist nicht Uniform function
> call syntax gemeint, sondern es geht um die Idempotenz von UDT und
> primitiven DT).

Bei der generischen Programmierung ist das Überladen von 
Operatorfunktionen
(das Definieren von Operationen) für UDT kein syntaktischer Zucker mehr,
sondern notwendig:
1
auto mean(auto a, auto b) {
2
     return (a + b) / 2;
3
}

Das obige Beispiel funktioniert nur für beliebige (UDT und prim. DT), 
wenn
man die eingebauten Operatoren für UDT definieren
(überladene Operatorfunktionen) kann.

In C++ ist der Name des struct / class der Datentyp-Name.
1
template<typename T>
2
auto mean(T a, T b) {
3
     return (a + b) / T{2};
4
}

T wird also hier zum Namen des struct / class (Typnamen) abgeleitet.

Müsste man nach wie vor ein Schlüsselwort struct davor schreiben, so
würde das Beispiel ja nicht mehr funktionieren, weil es
1
template<typename T>
2
auto mean(T a, T b) {
3
     return (a + b) / struct T{2};
4
}

heißen müsste.
Dies wäre eine weitere Unsymmetrie zwischen UDT und prim. DT, die die
generische Programmierung verhindern würde.

von W.S. (Gast)


Lesenswert?

DerEgon schrieb:
> Ein typedef verhält sich aber wie ein Typ, insofern ist die
> Unterscheidung ziemlich irrelevant.

Einen "typedef" gibt es nicht. Stattdessen gibt es eine in C eingeführte 
Anweisung
typedef alterName neuerName;
und das setzt eigentlich voraus, daß es für eine typedef-Anweisung einen 
bereits zuvor definierten Typ geben müßte. Bloß bei struct's hat man 
eine Ausnahme gemacht, dahingehend, daß besagter Typ (in diesem Fall ein 
struct) auch innerhalb der typedef-Anweisung hingeschrieben werden kann. 
Eben so, wie du es bereits geschrieben hast:
1
typedef struct 
2
{ int x;
3
  int y;
4
  int z;
5
} blafusel;

Wie man sieht, ist der eigentlich definierte struct namenlos, weswegen 
er im Programm auch nicht benutzt werden kann. Aber er kann über den mit 
typedef zugewiesenen Zweitnamen "blafusel" benutzt werden.

Also: die Bezeichnung "typedef" ist eigentlich irreführend. Es ist 
realiter nur ein "create_alias".

W.S.

von Mombert H. (mh_mh)


Lesenswert?

Rolf M. schrieb:
> Ich sehe da jetzt kein Problem mit Operatoren, Funktionen oder
> Templates.
Probleme sind relativ ;-).
1
struct S {
2
  int v;
3
};
4
5
template <class T, struct S>
6
auto add(T a, T b) -> T {
7
  return a + b;
8
};
9
10
void foo() {
11
  S sa = {1};
12
  S sb = {42};
13
  S sc = add<struct S, S{0}>(sa, sb);
14
}
Das kann sehr schnell sehr verwirrend werden.

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> T wird also hier zum Namen des struct / class (Typnamen) abgeleitet.
>
> Müsste man nach wie vor ein Schlüsselwort struct davor schreiben, so
> würde das Beispiel ja nicht mehr funktionieren, weil es
> template<typename T>
> auto mean(T a, T b) {
>      return (a + b) / struct T{2};
> }
>
> heißen müsste.

Nein, da 'struct' bereits Teil des Namens des Typs ist. Das kann man an 
der Stelle nicht noch ein zweites mal angeben.
Wenn T z.B. ein struct X ist, dann macht es keinen Sinn, zusätzlich noch 
"struct T" draus zu machen. Wie beim Typedef:
1
typedef struct X T;
2
struct T t;  // Fehler

W.S. schrieb:
> Einen "typedef" gibt es nicht. Stattdessen gibt es eine in C eingeführte
> Anweisung
> typedef alterName neuerName;
> und das setzt eigentlich voraus, daß es für eine typedef-Anweisung einen
> bereits zuvor definierten Typ geben müßte.

Es muss ein an dieser Stelle namentlich bekannter Typ sein. Ob der zuvor 
oder genau dort definiert wurde, ist egal. Er muss an der Stelle noch 
nicht mal überhaupt definiert sein. Es reicht schon aus, wenn er dort 
nur implizit deklariert wurde:
1
typedef struct X Y;
2
3
struct X { int i; };     
4
       
5
int main()
6
{       
7
    Y foo;
8
    foo.i = 3;
9
}

> Bloß bei struct's hat man eine Ausnahme gemacht, dahingehend, daß
> besagter Typ (in diesem Fall ein struct) auch innerhalb der typedef
> -Anweisung hingeschrieben werden kann.

Das ist keine Ausnahme, sondern ergibt sich automatisch, wenn man es 
nicht explizit verbietet.

> Also: die Bezeichnung "typedef" ist eigentlich irreführend. Es ist
> realiter nur ein "create_alias".

Richtig.

: Bearbeitet durch User
von Lars R. (larsr)


Lesenswert?

Wilhelm M. schrieb:
> Müsste man nach wie vor ein Schlüsselwort struct davor schreiben, so
> würde das Beispiel ja nicht mehr funktionieren

Nein, weil das T ja entweder "struct S" oder "int" heißt. Es müsste vor 
dem Template-Parameter nicht nochmals "struct" stehen.

Ich sehe daher auch keinen Grund, wieso dies die genetische 
Programmierung verhindern sollte...

von (prx) A. K. (prx)


Lesenswert?

Lars R. schrieb:
> wieso dies die genetische Programmierung verhindern sollte...

Zum C++ Compiler für Gene ists hoffentlich noch eine Weile hin. ;-)

von Lars R. (larsr)


Lesenswert?

(prx) A. K. schrieb:
> Zum C++ Compiler für Gene ists hoffentlich noch eine Weile hin. ;-)

Habe auf dem Smartphone getippt. Generisch korrigiert er mir ungewollt. 
Ich bitte um Verzeihung, hätte besser vor dem Absenden schauen müssen!

von Yalu X. (yalu) (Moderator)


Lesenswert?

Rolf M. schrieb:
> Wilhelm M. schrieb:
>> T wird also hier zum Namen des struct / class (Typnamen) abgeleitet.
>>
>> Müsste man nach wie vor ein Schlüsselwort struct davor schreiben, so
>> würde das Beispiel ja nicht mehr funktionieren, weil es
>> template<typename T>
>> auto mean(T a, T b) {
>>      return (a + b) / struct T{2};
>> }
>>
>> heißen müsste.
>
> Nein, da 'struct' bereits Teil des Namens des Typs ist. Das kann man an
> der Stelle nicht noch ein zweites mal angeben.

Lars R. schrieb:
> Nein, weil das T ja entweder "struct S" oder "int" heißt. Es müsste vor
> dem Template-Parameter nicht nochmals "struct" stehen.
>
> Ich sehe daher auch keinen Grund, wieso dies die genetische
> Programmierung verhindern sollte...

Ja, genau das ist der Punkt, warum ich oben ein Verständnisproblem
hatte, das sich jetzt aber in Wohlgefallen aufgelöst hat:

Yalu X. schrieb:
> Wilhelm M. schrieb:
>> (prx) A. K. schrieb:
>>> C++ hat das repariert, indem der Struct-Name zum Typedef-Name wurde.
>>
>> Naja, C++ hat da nichts repariert. Sondern es ist in C++ eine absolute
>> Notwendigkeit.
>
> Das verstehe ich nicht.

Wie es aussieht, kann man also auch in C++ vor alle Strukturnamen, die
nicht schon ein Alias sind (und damit das "struct" implizit enthalten),
problemlos ein "struct" davorsetzen, muss es im Gegensatz zu C aber
nicht.

Lässt man das "struct" weg, macht man damit deutlich, dass es sich
sowohl bei Strukturen als auch bei primitiven Typen um abstrakte
Datentypen (also Einheiten aus Daten und zugehörigen Operationen), und
damit um etwas logisch Ähnliches handelt.

Man verwischt damit allerdings auch die Unterschiede, die durchaus
bestehen:

- Von einer Struktur oder Klasse kann man einen neuen Typ ableiten, von
  einem primitiven Typ nicht.

- Auf die Member-Funktionen einer Struktur oder Klasse kann man per
  "."-Operator zugreifen, bei primitiven Datentypen geht das nicht.

Auf Grund dieser Unterschiede wäre IMHO auch nicht völlig verkehrt, das
(optionle) "struct" hinzuschreiben, wenngleich ich niemanden kenne, der
das tatsächlich tut.

von Mombert H. (mh_mh)


Lesenswert?

Yalu X. schrieb:
> Wie es aussieht, kann man also auch in C++ vor alle Strukturnamen, die
> nicht schon ein Alias sind (und damit das "struct" implizit enthalten),
> problemlos ein "struct" davorsetzen, muss es im Gegensatz zu C aber
> nicht.
Nein! Z.B. das struct nach dem return ist nicht erlaubt:
Rolf M. schrieb:
> auto operator+(struct S a, struct S b) -> struct S {
>   return struct S{a.v + b.v};
> }

Yalu X. schrieb:
> Auf Grund dieser Unterschiede wäre IMHO auch nicht völlig verkehrt, das
> (optionle) "struct" hinzuschreiben, wenngleich ich niemanden kenne, der
> das tatsächlich tut.

Ich glaube nicht, dass C++ dadurch lesbarer wird. Warum ist es außerhalb 
von templates so wichtig, dass man ein struct dran schreibt, in einem 
template aber nicht mehr? Man kann die spassigen Fälle haben, dass man 
ein template mit Argument "struct s" instanziert, dass in der Definition 
des templates aber ein "class T" Parameter ist, oder so aussieht. Und 
man kann generell nicht das gleiche mit Klassen machen, obwohl sie ja 
"nur struct mit 'private:' sind". ...

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Mombert H. schrieb:
> Yalu X. schrieb:
>> Wie es aussieht, kann man also auch in C++ vor alle Strukturnamen, die
>> nicht schon ein Alias sind (und damit das "struct" implizit enthalten),
>> problemlos ein "struct" davorsetzen, muss es im Gegensatz zu C aber
>> nicht.
> Nein! Z.B. das struct nach dem return ist nicht erlaubt:
> Rolf M. schrieb:
>> auto operator+(struct S a, struct S b) -> struct S {
>>   return struct S{a.v + b.v};
>> }

Das S in der return-Anweisung ist – anders als im Funktionskopf in der
Zeile darüber – nicht der Typname, sonder der Name des Konstruktors,
deswegen ist hier das "struct" fehl am Platz.

Mombert H. schrieb:
> Yalu X. schrieb:
>> Auf Grund dieser Unterschiede wäre IMHO auch nicht völlig verkehrt, das
>> (optionle) "struct" hinzuschreiben, wenngleich ich niemanden kenne, der
>> das tatsächlich tut.
>
> Ich glaube nicht, dass C++ dadurch lesbarer wird. Warum ist es außerhalb
> von templates so wichtig, dass man ein struct dran schreibt, in einem
> template aber nicht mehr?

Es ist überhaupt nicht wichtig, deswegen tut das ja auch niemand (mich
eingeschlossen). Andererseits sind für mich Wilhelms Argumente für das
Weglassen des "struct" bzw. für die "absolute Notwendigkeit", dies zu
tun, aus den in meinem letzten Beitrag genannten Gründen auch nicht
hundertprozentig stichhaltig.

Da man sich aber letztendlich für eine der beiden Alternativen (mit oder
ohne "struct") entscheiden muss und logische Überlegungen dabei nur
begrenzt weiterhelfen, geht man am besten ganz pragmatisch vor und wählt
die Option mit der wenigsten Tipparbeit, die auch gleichzeitig die in
den C++Lehrbüchern propagierte ist :)

von Mombert H. (mh_mh)


Lesenswert?

Yalu X. schrieb:
> Das S in der return-Anweisung ist – anders als im Funktionskopf in der
> Zeile darüber – nicht der Typname, sonder der Name des Konstruktors,
> deswegen ist hier das "struct" fehl am Platz.
Bist du dir sicher, dass da der Name des Konstruktors steht? Welcher 
Konstruktor wird aufgerufen? Was passiert, wenn S einen S(int) 
Konstruktor hätte und die Funktion so aussieht:
1
auto operator+(S a, S b) -> S {
2
  return S(a.v + b.v);
3
}
Was passiert, wenn die Funktion einen int zurück liefert?
1
auto operator+(S a, S b) -> int {
2
  return int(a.v + b.v);
3
}
Welcher Konstruktor wird hier aufgerufen?

Yalu X. schrieb:
> Es ist überhaupt nicht wichtig, deswegen tut das ja auch niemand (mich
> eingeschlossen).
Seid wann muss etwas wichtig sein, damit wir hier über Vor- und 
Nachteile diskutieren? ;-) Auch wenn es nicht wichtig ist, ich habe 
schonmal etwas gelernt (Ich bin bis jetzt davon ausgegangen, dass hinter 
dem return ein expliziter Aufruf eines Konstruktors steht und hatte bis 
jetzt noch keinen Grund diese Annahme zu überdenken. Vielen Dank für 
diese Anregung! :) )

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Yalu X. schrieb:
> Das S in der return-Anweisung ist – anders als im Funktionskopf in der
> Zeile darüber – nicht der Typname, sonder der Name des Konstruktors,
> deswegen ist hier das "struct" fehl am Platz.

Das ist hier nicht der Grund. An dieser Stelle wird ja ein Ausdruck 
erwartet, jedoch wird `struct S` nicht als Typ geparsed, hier müssen wir 
dem Compiler wieder etwas helfen:
1
return (struct S){42};

Also die Assoziativität der Tokens verändern. Dann geht es wieder.

Mombert H. schrieb:
> Bist du dir sicher, dass da der Name des Konstruktors steht? Welcher
> Konstruktor wird aufgerufen? Was passiert, wenn S einen S(int)
> Konstruktor hätte und die Funktion so aussieht:auto operator+(S a, S b)
> -> S {
>   return S(a.v + b.v);
> }
> Was passiert, wenn die Funktion einen int zurück liefert?auto
> operator+(S a, S b) -> int {
>   return int(a.v + b.v);
> }
> Welcher Konstruktor wird hier aufgerufen?

Es wird `S(int)`, d.h. der Typwandlungs-ctor bzw. int(int), d.h. der 
c-ctor aufgerufen.

Mombert H. schrieb:
> (Ich bin bis jetzt davon ausgegangen, dass hinter
> dem return ein expliziter Aufruf eines Konstruktors steht und hatte bis
> jetzt noch keinen Grund diese Annahme zu überdenken.

Nein, hinter return muss ein Ausdruck stehen.

Eine weitere Inkonsistenz ist, dass der Template-Typ-Parameter zu `S` 
abgeleitet wird, selbst wenn das template mit `struct S` parametriert 
wird. Insgesamt sieht man daran, dass diese Möglichkeit, den Typnamen 
mit dem struct-Namen (das ist nicht der Typ) gleichzusetzen, bei C++ aus 
Kompatibilitätsgründen zu C da ist.

von Wilhelm M. (wimalopaan)


Lesenswert?

Es gibt allerdings auch den umgekehrten Fall in C++, wo das 
Schlüsselwort `struct` als disambiguation keyword notwendig ist (hier 
dann also nicht aus Kompatibilitätsgründen zu C, sondern für C++ selbst 
notwendig):
1
struct S1 {
2
    S1(int x = 0) : x{x} {}
3
    int x{};
4
};
5
6
void S1() {
7
}
8
9
template<typename T>
10
auto foo(T a) {
11
//    T::_;
12
    return (struct S1){a}; // Assoziativität (notwendig bei Ausdrücken)
13
}
14
15
int main() {
16
    foo((struct S1){}); // Assoziativität (notwendig bei Ausdrücken)
17
    
18
    struct S1 s1; // Disambiguation: S1 s1; funktioniert nicht mehr, wenn Funktion S1() deklariert ist
19
    foo(s1);
20
}

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Es gibt allerdings auch den umgekehrten Fall

Ein schönes Beispiel dafür, daß man nicht alles, was geht, auch 
tatsächlich tun sollte.

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> Wilhelm M. schrieb:
>> Es gibt allerdings auch den umgekehrten Fall
>
> Ein schönes Beispiel dafür, daß man nicht alles, was geht, auch
> tatsächlich tun sollte.

Genau, auch wenn es hier gar nicht darum ging, sondern nur um 
Möglichkeiten.

Und dies ist auch eher ein C als ein C++ Thema, denn in C gibt es keine 
Namensräume wie in C++, mit denen man solche Namenskollisionen (zusammen 
mit ADL) entschärfen könnte.

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.