Forum: PC-Programmierung _Generic C11 benötige Hilfe


von Tester (Gast)


Lesenswert?

hi,
ich habe hier 2 Funktionen die ich mit dem _Generic Keyword 'verbinden' 
möchte.
1
#define FOO_EQ( a, b)   _Generic( (a),                                          \
2
                            uint32_t:   _Generic( (b), uint32_t:   FOO_EQ_U32), \
3
                            int32_t:    _Generic( (b),  int32_t:   FOO_EQ_S32))( (a), (b))

und hier die Funktionen :
1
bool FOO_EQ_U32( uint32_t a, uint32_t b);
2
bool FOO_EQ_S32( int32_t a, int32_t b);
3
4
bool FOO_EQ_U32( uint32_t a, uint32_t b)
5
{
6
   return ( a == b );
7
}
8
9
bool FOO_EQ_S32( int32_t a, int32_t b)
10
{
11
   return ( a == b );
12
}

Es soll sicher gestellt sein das a und b immer den gleichen Datentyp 
haben.
Doch leider geht das so nicht.
1
bool isEQ= FOO_EQ( 1U, 2U );
2
//Error[Pe2535]: no association matches the selector type "unsigned int" C:\Source\file.c 401

Compiler IAR. C11
 8.20.1.14183 (8.20.1.14183)

diese Version geht:
1
#define FOO_EQ2( a, b)   _Generic( (a),                                          \
2
                            uint32_t:   _Generic( (b), uint32_t:   FOO_EQ_U32), \
3
                            int32_t:    FOO_EQ_S32)( (a), (b))  
4
5
bool isEQ= FOO_EQ2( 1U, 2U );

Vielleicht fällt den PC Programmieren was auf, was in der oberen Version 
falsch ist.
Danke.

von Dr. Sommer (Gast)


Lesenswert?

Sollte C++ auch akzeptabel sein, da geht das ganz einfach so:
1
bool FOO_EQ( uint32_t a, uint32_t b)
2
{
3
   return ( a == b );
4
}
5
6
bool FOO_EQ( int32_t a, int32_t b)
7
{
8
   return ( a == b );
9
}
10
bool isEQ= FOO_EQ( 1U, 2U );
11
bool isEQ= FOO_EQ( 1, 2 );

von Wilhelm M. (wimalopaan)


Lesenswert?

Dr. Sommer schrieb:
> Sollte C++ auch akzeptabel sein, da geht das ganz einfach so:
>
1
bool FOO_EQ( uint32_t a, uint32_t b)
2
> {
3
>    return ( a == b );
4
> }
5
> 
6
> bool FOO_EQ( int32_t a, int32_t b)
7
> {
8
>    return ( a == b );
9
> }
10
> bool isEQ= FOO_EQ( 1U, 2U );
11
> bool isEQ= FOO_EQ( 1, 2 );

Geht leider nicht wegen impl. Typwandlungen -> ambigous

von Wilhelm M. (wimalopaan)


Lesenswert?

Besser so in C++:
1
template<typename T>
2
bool FOO_EQ( T a, T b)
3
{
4
   return ( a == b );
5
}

von Dr. Sommer (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Geht leider nicht wegen impl. Typwandlungen -> ambigous
Bei mir geht das:
1
#include <cstdint>
2
#include <iostream>
3
4
bool FOO_EQ( uint32_t a, uint32_t b)
5
{
6
  std::cout << "unsigned compare\n";
7
   return ( a == b );
8
}
9
10
bool FOO_EQ( int32_t a, int32_t b)
11
{
12
  std::cout << "signed compare\n";
13
   return ( a == b );
14
}
15
16
int main () {
17
  bool isEQ1= FOO_EQ( 1U, 2U );
18
  bool isEQ2= FOO_EQ( 1, 2 );
19
  
20
}
ergibt:
unsigned compare
signed compare

Sowas wie "FOO_EQ( 1, 2U )" geht natürlich nicht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Tester schrieb:
> Es soll sicher gestellt sein das a und b immer den gleichen Datentyp
> haben.

In GCC ginge sowas:
1
__attribute__((__error__("bad, really bad")))
2
extern bool bad1 (uint32_t, int32_t);
3
4
__attribute__((__error__("sad, really sad")))
5
extern bool bad2 (int32_t, uint32_t);
6
7
#define FOO_EQ(a, b)                                    \
8
    _Generic ((a),                                      \
9
              uint32_t: _Generic ((b),                  \
10
                                  uint32_t: FOO_EQ_U32, \
11
                                  int32_t : bad1),      \
12
              int32_t:  _Generic ((b),                  \
13
                                  int32_t: FOO_EQ_S32,  \
14
                                  uint32_t: bad2))      \
15
    ((a), (b))
16
17
...
18
#line 42
19
bool use_su (int32_t a, uint32_t b)  { return FOO_EQ (a, b); }
20
bool use_uu (uint32_t a, uint32_t b) { return FOO_EQ (a, b); }
21
bool use_us (uint32_t a, int32_t b)  { return FOO_EQ (a, b); }
22
bool use_ss (int32_t a, int32_t b)   { return FOO_EQ (a, b); }

Das führt dazu, dass der Code gegen bad1 bzw. bad2 linken will, was von 
gcc mit einem Fehler quittiert wird:
1
gen.c: In function 'use_su':
2
gen.c:42:45: error: call to 'bad2' declared with attribute error: sad, really sad
3
gen.c: In function 'use_us':
4
gen.c:44:45: error: call to 'bad1' declared with attribute error: bad, really bad
 
Falls es kein Äquivalent zu Attribute "error" gibt, wäre der nächste, 
der warnen kann, der Assembler.  In GNU-Speek:
 
1
static inline bool bad1 (uint32_t a, int32_t b)
2
{
3
    __asm (".error \"bad, really bad: %0 == %1\"" :: "r" (a), "r" (b));
4
    abort();
5
}
6
7
static inline bool bad2 (int32_t a, uint32_t b)
8
{
9
    __asm (".error \"sad, really sad: %0 == %1\"" :: "r" (a), "r" (b));
10
    abort();
11
}
 
1
gen.s: Assembler messages:
2
gen.s:55: Error: sad, really sad: r22 == r18
3
gen.s:88: Error: bad, really bad: r22 == r18
 
Und falls das auch nicht gibt, lässt man den Linker ins Leere laufen:
 
1
extern bool bad1 (uint32_t, int32_t);
2
extern bool bad2 (int32_t, uint32_t);
 
1
gen.o: In function `use_su':
2
gen.c:(.text+0x24): undefined reference to `bad2'
3
gen.o: In function `use_us':
4
gen.c:(.text+0x38): undefined reference to `bad1'

von Wilhelm M. (wimalopaan)


Lesenswert?

Was ist das für ein Krampf ...

Da ist das C++-Template doch wesentlich einfacher ;-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Was ist das für ein Krampf ...
>
> Da ist das C++-Template doch wesentlich einfacher ;-)

Es geht um C11, nicht um C++.

von Wilhelm M. (wimalopaan)


Lesenswert?

Und ich wage zu vermuten, dass das, was der TO machen will, auch als C++ 
übersetzbar wäre ...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Und ich wage zu vermuten, dass das, was der TO machen will, auch als C++
> übersetzbar wäre ...

Und wenn es als Python oder Haskell oder BrainFuck übersetzbar wäre: 
Spielt das eine Rolle?

Es geht um C11.

Und die Applikation des TO besteht vermutlich aus mehr als den nur 
duzend Zeilen des Testfalls.

von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:
> Wilhelm M. schrieb:
>> Und ich wage zu vermuten, dass das, was der TO machen will, auch als C++
>> übersetzbar wäre ...
>
> Und wenn es als Python oder Haskell oder BrainFuck übersetzbar wäre:
> Spielt das eine Rolle?
>
> Es geht um C11.
>
> Und die Applikation des TO besteht vermutlich aus mehr als den nur
> duzend Zeilen des Testfalls.

Ich bin der Meinung, dass man Fragen auch durchaus einmal unter einem 
anderen Gesichtspunkt als dem des Fragenden beantworten darf, denn der 
ist oftmals in seinem eigenen Gedankengebäude gefangen. In diesem Sinne 
darf der Hinweis auf eine eng verwandte Sprache erlaubt sein, in der 
es gerade für das konkrete Problem des Fragenden einen Mechanismus gibt: 
hier das template-System.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Johann L. schrieb:
>> Wilhelm M. schrieb:
>>> Und ich wage zu vermuten, dass das, was der TO machen will, auch als C++
>>> übersetzbar wäre ...
>>
>> Und wenn es als Python oder Haskell oder BrainFuck übersetzbar wäre:
>> Spielt das eine Rolle?
>>
>> Es geht um C11.
>>
>> Und die Applikation des TO besteht vermutlich aus mehr als den nur
>> duzend Zeilen des Testfalls.
>
> Ich bin der Meinung, dass man Fragen auch durchaus einmal unter einem
> anderen Gesichtspunkt als dem des Fragenden beantworten darf, denn der

Eine C++ Diskussion ist keine Beantwortung einer Frage zu C11.

> ist oftmals in seinem eigenen Gedankengebäude gefangen.

Ich habe eher den Eindruck, das Problem ist das manche in C++ gefangen 
sind, und zu allem ihren C++ Senf geben (müssen?).

> In diesem Sinne darf der Hinweis auf eine eng verwandte Sprache

Okay, also auch Objective-C und B und D...

Und in deinem eigenen "Fütter"-Thread wirst du garstig, wenn jemand mehr 
als einen Link postet.

> erlaubt sein,

von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:

> Okay, also auch Objective-C und B und D...

Warum nicht, it's up to you ...

>
> Und in deinem eigenen "Fütter"-Thread wirst du garstig, wenn jemand mehr
> als einen Link postet.

Und der ist genau zu diesem Zweck - Sammlung, kein Diskussion - eröffnet 
worden.

Wenn der TO sich hier zu Wort meldet, und keine Hinweise mehr auf etwas 
anderes als C11 haben möchte, dann halte ich ich gerne zurück ;-)

von Nop (Gast)


Lesenswert?

Also, zum Ausgangsposting: offenbar werden die Ausdrücke nicht wie ein 
Switch-Case im Programm abgearbeitet, sondern von innen nach außen 
expandiert. Das wäre ja bei einem Parser auch zu erwarten.

Wenn man das jetzt macht, also erstmal alle inneren Makros abarbeitet, 
dann geht das für die Zeile mit den uint32_t glatt, aber bei der 
int32_t-Zeile scheitert das, weil b eben kein int32_t ist.

Deswegen geht auch die zweite Variante, weil da kein inneres Generic 
ist, was beim Expandieren scheitern würde.

Beitrag #5228773 wurde von einem Moderator gelöscht.
Beitrag #5228774 wurde von einem Moderator gelöscht.
von Nop (Gast)


Lesenswert?

1
#define FOO_EQ( a, b)   _Generic( (a),                                          \
2
                            uint32_t:   _Generic( (b), uint32_t:   FOO_EQ_U32, default: NULL), \
3
                            int32_t:    _Generic( (b),  int32_t:   FOO_EQ_S32, default: NULL),  \
4
                            default: NULL )( (a), (b))

Verdammt, mit der Formatierung hab ich's aber auch.

von Tester (Gast)


Lesenswert?

Danke Nop,
dein Code funktioniert.

Aber mit der Erklärung komme ich nicht ganz klar.
Ich muß mir das nochmal neher ansehen....

mfg

von Nop (Gast)


Lesenswert?

Tester schrieb:

> Aber mit der Erklärung komme ich nicht ganz klar.

Also im Programm wird ein verschachtelter Switch-Case ja von oben nach 
unten durchlaufen. Wenn der äußere case zutrifft und man da noch innere 
Switch-Case drinnen hat, wird nur der durchlaufen, wo der äußere schon 
paßt.
1
int outer = 1, inner = 2, result = 0;
2
3
switch (outer)
4
{
5
  case 1:
6
  switch (inner)
7
  {
8
    case 1:
9
      result = 1;
10
    break;
11
    case 2:
12
      result = 2;
13
    break;
14
  }
15
  break;
16
  case 2:
17
  switch (inner)
18
  {
19
    case 1:
20
      result = 3;
21
    break;
22
    case 2:
23
      result = 4;
24
    break;
25
  }
26
  break;
27
}

Hier geht es oben los, der Programmfluß geht im oberen "case 1" rein und 
betritt das inner switch-case. Da gibt's dann den Match bei case 2, und 
result wird auf 2 gesetzt. Das ist imperative Programmierung, 
sequentiell und Schritt für Schritt.

Das Verführerische bei Deinen Makros ist jetzt, daß Generics eine Art 
einfaches switch-case für Types sind. Das ist aber nur rein logisch 
gesehen so, aber nicht von der Umsetzung. Es ist ein Makro, damit gibt 
es da keinen Programmablauf. Expandiert wird schließlich im 
Präprozessor. Man hat hier vielmehr verschachtelte Ausdrücke.

Ein Ausdruck wie
2 * ((3 + 4) / (5 + 6))
wird nicht von links nach rechts ausgewertet in dem Sinne, sondern von 
innen nach außen. Es wird also erstmal das Ergebnis der innersten 
Klammern bestimmt, dann das der äußeren, und zuletzt multipliziert.

Und genau das haste bei den Makros auch - keinen Programmablauf, sondern 
die Auswertung geklammerter Ausdrücke.

Beitrag #5229525 wurde vom Autor gelöscht.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Nop schrieb:
> Es ist ein Makro,

_Generic ist kein Makro.  Es ist ein neues Schlüselwort ab C11.

von Nop (Gast)


Lesenswert?

Johann L. schrieb:

> _Generic ist kein Makro.  Es ist ein neues Schlüselwort ab C11.

Naja in dem Kontext mit den defines meinte ich halt. Jedenfalls sind es 
Ausdrücke, keine Abläufe.

Btw., die Nummer mit dem "default: NULL" ist ein quick&dirty-Hack, der 
nur zur Verdeutlichung des Prinzips gedacht ist und nicht zur Übernahme 
in eine reale Anwendung. Die Konsequenz wäre nämlich ein Programmabsturz 
(Segfault), wenn a und b nicht beide int32_t oder beide uint32_t sind.

von Wilhelm M. (wimalopaan)


Lesenswert?

Das Problem an der ganzen Sache ist, dass der C-Präprozessor (CPP) ein 
nicht interaktiver Editor ist. Der Compiler übersetzt also nicht das, 
was man geschrieben hat, sondern das, was der CPP daraus gemacht hat.

Im Beispiel:
1
#include <stdint.h>
2
#include <stdbool.h>
3
4
void bad1();
5
void bad2();
6
void bad3();
7
8
bool FOO_EQ_U32( uint32_t a, uint32_t b) {
9
   return ( a == b );
10
}
11
12
bool FOO_EQ_S32( int32_t a, int32_t b) {
13
   return ( a == b );
14
}
15
16
#define FOO_EQ_NOK(a, b)   _Generic((a), \
17
                            uint32_t:  _Generic((b), \
18
                                           uint32_t:   FOO_EQ_U32 \
19
                                       ), \
20
                            int32_t:   _Generic((b), \
21
                                           int32_t:   FOO_EQ_S32 \
22
                                        ) \
23
                           ) ((a),(b))
24
25
#define FOO_EQ_OK(a, b)   _Generic((a), \
26
                            uint32_t:  _Generic((b), \
27
                                           uint32_t:  FOO_EQ_U32, \
28
                                           default: bad1 \
29
                                       ), \
30
                            int32_t:   _Generic((b), \
31
                                           int32_t:   FOO_EQ_S32, \
32
                                           default: bad2 \
33
                                        ), \
34
                            default: bad3 \
35
                           ) ((a),(b))
36
37
38
int main() {
39
//    bool isEQ = FOO_EQ_NOK((uint32_t)1, (uint32_t)(2));
40
41
    uint32_t x = 0;
42
    int32_t y = 0;
43
    
44
    bool isEQ = FOO_EQ_NOK(x, x);
45
    
46
    bool isEQ = FOO_EQ_OK(x, x);
47
    
48
}

wird daraus:
1
# 38 "bm100a.c"
2
int main() {
3
4
5
    uint32_t x = 0;
6
    int32_t y = 0;
7
8
    
9
# 44 "bm100a.c" 3 4
10
   _Bool 
11
# 44 "bm100a.c"
12
        isEQ = _Generic((x), uint32_t: _Generic((x), uint32_t: FOO_EQ_U32 ), int32_t: _Generic((x), int32_t: FOO_EQ_S32 ) ) ((x),(x));
13
14
    
15
# 46 "bm100a.c" 3 4
16
   _Bool 
17
# 46 "bm100a.c"
18
        isEQ = _Generic((x), uint32_t: _Generic((x), uint32_t: FOO_EQ_U32, default: bad1 ), int32_t: _Generic((x), int32_t: FOO_EQ_S32, default: bad2 ), default: bad3 ) ((x),(x));
19
20
}

und damit auch sofort klar, warum das ursprüngliche Macro nicht gehen 
kann (s.a. alte Regel: schreibe nie CPP-Macros, die wie Funktionen 
aussehen ...)

Der zweite Fallstrick am _Generic ist, das der default-Teil ebenfalls 
instanziiert wird. Dort muss also ein wohlgeformter Ausdruck stehen. 
Deswegen habe ich das Beispiel oben so formuliert, dass dort die Symbole 
bad1, 2, 3 auftauchen. Sind die nicht deklariert, gibt es bei der 
Auswertung des _Generic ein Fehler ...

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.