Forum: Mikrocontroller und Digitale Elektronik Verständins Problem mit ENUM in "C"


von Martin M. (ats3788)


Lesenswert?

Hallo liebe C Gemeinde
Ich habe da ein enum i2s_mode_t;


typedef enum {
    I2S_MODE_MASTER = 1,
    I2S_MODE_SLAVE = 2,
    I2S_MODE_TX = 4,
    I2S_MODE_RX = 8,
    I2S_MODE_DAC_BUILT_IN = 16,       /*!< Output I2S data to built-in 
DAC, no matter the data format is 16bit or 32 bit, the DAC module will 
only take the 8bits from MSB*/
    I2S_MODE_ADC_BUILT_IN = 32,       /*!< Input I2S data from built-in 
ADC, each data can be 12-bit width at most*/
    I2S_MODE_PDM = 64,
} i2s_mode_t;


i2s_mode_t I2SMode;



I2SMode = I2S_MODE_MASTER | I2S_MODE_TX; // Das geht nicht !!!!!!!

die Orginal Syntax sieht folgendermassen aus


  I2S_Mode  = {
      .mode = I2S_Mode,     // I2S_MODE_MASTER | I2S_MODE_TX, 
// Only TX
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 
//16-bit per channel
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 
//2-channels
    .communication_format = I2S_COMM_FORMAT_I2S | 
I2S_COMM_FORMAT_I2S_MSB,
    .dma_buf_count = 6,
    .dma_buf_len = 60, 
//
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1 
//Interrupt level 1
  };

In Pascal macht man Mehrzuweisungen mit einem Set of
http://wiki.freepascal.org/Set

wie macht man das in C

von Bernd K. (prof7bit)


Lesenswert?

Martin M. schrieb:
> I2SMode = I2S_MODE_MASTER | I2S_MODE_TX; // Das geht nicht !!!!!!!

Warum soll das nicht gehen?

von Thomas E. (picalic)


Lesenswert?

Das geht deshalb nicht, weil durch die Typdefinition dem Compiler 
explizit mitgeteilt wurde, daß I2SMode nur einen der Werte {1, 2, 4, 16, 
32, 64} annehmen kann.

I2S_MODE_MASTER | I2S_MODE_TX ist 5 und demnach außerhalb des gültigen 
Wertebereichs für diesen Typ.

Mit Pascal kenne ich mich zwar nicht aus, aber dort müsste der Compiler 
ebenfalls meckern, wenn man der Variablen einen Wert außerhalb des 
spezifizierten "Set of" zuweisen will.

: Bearbeitet durch User
von Christian H. (netzwanze) Benutzerseite


Lesenswert?

Martin M. schrieb:
> wie macht man das in C

Einfach mittels #define

von g457 (Gast)


Lesenswert?

Klar geht das wenn mans richtig hinschreibt oder die Fehlermeldung 
liest:
1
$ cat test.c 
2
typedef enum {
3
    I2S_MODE_MASTER = 1,
4
    I2S_MODE_SLAVE = 2,
5
    I2S_MODE_TX = 4,
6
    I2S_MODE_RX = 8,
7
    I2S_MODE_DAC_BUILT_IN = 16,       /*!< Output I2S data to built-in DAC, no matter the data format is 16bit or 32 bit, the DAC module will only take the 8bits from MSB*/
8
    I2S_MODE_ADC_BUILT_IN = 32,       /*!< Input I2S data from built-in ADC, each data can be 12-bit width at most*/
9
    I2S_MODE_PDM = 64,
10
} i2s_mode_t;
11
12
13
i2s_mode_t I2SMode = I2S_MODE_MASTER | I2S_MODE_TX; // <- desch gehd
14
15
16
void f()
17
{
18
        I2SMode = I2S_MODE_MASTER | I2S_MODE_TX; // <- desch gehd ahh
19
}
20
21
$ gcc -Wall -Wextra -pedantic -std=c99 -c -o test test.c 
22
$ file test
23
test: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

Dass der Wert "zu groß" ist zwar ist formell richtig (aber hier nicht 
das Problem), allerdings gibt es meines Wissens keinen C-Cimpiler auf 
dieser Welt, der das nicht trotzdem richtig machen würde. Wenn man 
standardkonform sein will muss man noch einen Enumerator definieren, in 
den alles rein passt (hier also mindestens den Wert 127 hat).

von Thomas E. (picalic)


Lesenswert?

(Die 8 habe ich natürlich vergessen! -> {1, 2, 4, 8, 16,
32, 64})

Eine saubere Lösung wäre ggf., im enum alle in Frage kommenden 
Kombinationen zu definieren, also z.B. I2S_MODE_MASTER_TX = 5.
Sonst halt die Variable als standard-Typ deklarieren (z.B. uint8_t) und 
enum nur zur Definition der Bitmasken-Werte benutzen.

von Carl D. (jcw2)


Lesenswert?

Welcher C-Compiler gibt denn als Fehlermeldung "Das geht nicht!" aus?
Solange Compiler-Aufruf und Ausgabe geheim bleiben, glaub ich gar 
nichts.

von Martin M. (ats3788)


Lesenswert?

Hallo und Danke für die Antworten.
Thomas E.
Du hast natürlich recht, deswegen habe ich ja auch den Link von Set of 
dazugefügt. Dann haben wir eine Menge und können mehrere Type 
Definitionen
auswählen.

Ich kann und will den Source nicht ändern das ist im ESP32 Arduino Core 
so definiert. Der Compiler
meckert mit SP32_I2S.ino: 56:10: error: invalid conversion from 'int' to 
'i2s_mode_t'
Ich frag mich nun geht das in Standard C ja oder nein.

von Thomas E. (picalic)


Lesenswert?

g457 schrieb:
> Dass der Wert "zu groß" ist zwar ist formell richtig

Nach meiner Auffassung ist der Wert nicht "zu groß", sondern schlicht 
nicht erlaubt!

Wenn ich schreibe:
1
typedef enum {
2
Montag = 1,
3
Dienstag = 2,
4
Mittwoch = 4,
5
Donnerstag = 8,
6
Freitag = 16,
7
Samstag = 32,
8
Sonntag = 64
9
} Wochentag_t;
10
11
Wochentag_t Tag;
Und wenn ich dann Tag den Wert 5 zuweise, ist der Compiler in meinen 
Augen fehlerhaft, wenn er das erlaubt!

von Dr. Sommer (Gast)


Lesenswert?

Thomas E. schrieb:
> Und wenn ich dann Tag den Wert 5 zuweise, ist der Compiler in meinen
> Augen fehlerhaft, wenn er das erlaubt!

Nein, das ist in C explizit erlaubt. Ein "enum i2s_mode_t" ist nur ein 
alias für "int", und "I2S_MODE_MASTER" ist nur eine int-Konstante (wobei 
C keine echten Konstanten hat, C++ schon).

Das hier:
1
typedef enum {
2
    I2S_MODE_MASTER = 1,
3
    I2S_MODE_SLAVE = 2,
4
    I2S_MODE_TX = 4,
5
    I2S_MODE_RX = 8,
6
    I2S_MODE_DAC_BUILT_IN = 16,       /*!< Output I2S data to built-in 
7
DAC, no matter the data format is 16bit or 32 bit, the DAC module will 
8
only take the 8bits from MSB*/
9
    I2S_MODE_ADC_BUILT_IN = 32,       /*!< Input I2S data from built-in 
10
ADC, each data can be 12-bit width at most*/
11
    I2S_MODE_PDM = 64,
12
} i2s_mode_t;
13
14
15
i2s_mode_t I2SMode;
16
17
int main () {
18
  I2SMode = I2S_MODE_MASTER | I2S_MODE_TX;
19
  return 0;
20
}
Kompiliert im GCC und im Clang auf maximaler Pingeligkeit problemlos. Es 
wird lediglich das überflüssige Komma am Ende des enums bemängelt.

Was für einen Compiler nutzt du denn? Kannst du Standard-Konformität 
einschalten? Ist das vielleicht eine MISRA-Warnung?

In C++11 kann man mit "enum class" solche Konvertierungen explizit 
verhindern; aber ich denke mal darum geht's hier nicht...

von Tom (Gast)


Lesenswert?

Arduino ist C++.

von Carl D. (jcw2)


Lesenswert?

Martin M. schrieb:
> Hallo und Danke für die Antworten.
> Thomas E.
> Du hast natürlich recht, deswegen habe ich ja auch den Link von Set of
> dazugefügt. Dann haben wir eine Menge und können mehrere Type
> Definitionen
> auswählen.
>
> Ich kann und will den Source nicht ändern das ist im ESP32 Arduino Core
> so definiert. Der Compiler
> meckert mit SP32_I2S.ino: 56:10: error: invalid conversion from 'int' to
> 'i2s_mode_t'
> Ich frag mich nun geht das in Standard C ja oder nein.

Das Problem ist: es handelt sich nicht um C-Code!
Arduino ist C++ und da werden Typen etwas weniger relaxed behandelt.
probier's mal so:
1
I2SMode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX );

von Bernd K. (prof7bit)


Lesenswert?

Thomas E. schrieb:
> Und wenn ich dann Tag den Wert 5 zuweise, ist der Compiler in meinen
> Augen fehlerhaft, wenn er das erlaubt!

In C sind Enums nicht wirklich typsicher, und ganz allgemein ist 
Typsicherheit nicht unbedingt das was C als Sprache auszeichnet. Böse 
Zungen  würden also sagen (insbesondere wenn sie einen 
Pascal-Hintergrund haben) nicht der Compiler ist fehlerhaft, die ganze 
Sprache ist ein einziger Fehler.

von Dr. Sommer (Gast)


Lesenswert?

PS: Ich sehe gerade:

Martin M. schrieb:
> Ich kann und will den Source nicht ändern das ist im ESP32 Arduino Core
> so definiert. Der Compiler

Arduino nutzt C++, also geht es gar nicht um C. C++ ist hier etwas 
strenger. Schreibe einfach:
1
I2SMode = static_cast<i2s_mode_t> (I2S_MODE_MASTER | I2S_MODE_TX);

Da es sowieso C++ ist geht auch sowas:
1
#include <cstdint>
2
3
using i2s_mode_t = uint8_t;
4
static constexpr uint8_t I2S_MODE_MASTER = 1;
5
static constexpr uint8_t I2S_MODE_SLAVE = 2;
6
static constexpr uint8_t I2S_MODE_TX = 4;
7
static constexpr uint8_t I2S_MODE_RX = 8;
8
static constexpr uint8_t I2S_MODE_DAC_BUILT_IN = 16;
9
static constexpr uint8_t I2S_MODE_ADC_BUILT_IN = 32;
10
static constexpr uint8_t I2S_MODE_PDM = 64;
11
12
i2s_mode_t I2SMode;
13
14
int main () {
15
  I2SMode= I2S_MODE_MASTER | I2S_MODE_TX;
16
  return 0;
17
}

Oder noch besser:
1
#include <type_traits>
2
#include <cstdint>
3
4
enum class i2s_mode_t : uint8_t {
5
  NONE = 0,
6
    MASTER = 1,
7
    SLAVE = 2,
8
    TX = 4,
9
    RX = 8,
10
    DAC_BUILT_IN = 16,       /*!< Output I2S data to built-in 
11
DAC, no matter the data format is 16bit or 32 bit, the DAC module will 
12
only take the 8bits from MSB*/
13
    ADC_BUILT_IN = 32,       /*!< Input I2S data from built-in 
14
ADC, each data can be 12-bit width at most*/
15
    PDM = 64,
16
};
17
18
inline i2s_mode_t operator | (i2s_mode_t lhs, i2s_mode_t rhs) {
19
  return static_cast<i2s_mode_t> (static_cast<std::underlying_type_t<i2s_mode_t>> (lhs) | static_cast<std::underlying_type_t<i2s_mode_t>> (rhs));
20
}
21
22
inline i2s_mode_t operator & (i2s_mode_t lhs, i2s_mode_t rhs) {
23
  return static_cast<i2s_mode_t> (static_cast<std::underlying_type_t<i2s_mode_t>> (lhs) & static_cast<std::underlying_type_t<i2s_mode_t>> (rhs));
24
}
25
26
int main () {
27
  i2s_mode_t I2SMode= i2s_mode_t::MASTER | i2s_mode_t::TX;
28
  
29
  // ...
30
  
31
  if ((I2SMode & i2s_mode_t::TX) != i2s_mode_t::NONE) {
32
    // Transmitter einschalten ...
33
  }
34
}

Warum überhaupt die "typedef enum"-Konstruktion? Dies ist nur in C 
nötig, nicht in C++.

von Thomas E. (picalic)


Lesenswert?

Bernd K. schrieb:
> In C sind Enums nicht wirklich typsicher, und ganz allgemein ist

Ok, aber zumindest meckert der Compiler bei "Wochentag = 5"

Aber auch bei "Wochentag = 4" oder "Wochentag = Montag | Dienstag".

Nur durch Casten kann man die Fehlermeldung verhindern:

"Tag = (Wochentag_t)5;" oder "Tag = (Wochentag_t)(Montag | Dienstag);"
compiliert ohne Fehlermeldung.

Insofern habt ihr Recht, daß der Compiler nicht wirklich auf den 
erlaubten Wertebereich prüft, sondern nur auf formale Typendefinitionen.

: Bearbeitet durch User
von Martin M. (ats3788)


Lesenswert?

Habe eine Lösung gefunden
So geht es

I2S_Mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);

Danke für eure Hilfe!!!

von Dr. Sommer (Gast)


Lesenswert?

Martin M. schrieb:
> I2S_Mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);

Lieber einen named cast nutzen:
1
I2S_Mode = static_cast<i2s_mode_t>(I2S_MODE_MASTER | I2S_MODE_TX);
Dann ist klarer was passiert, und man kann weniger falsch machen.

https://stackoverflow.com/a/1609185

von c-hater (Gast)


Lesenswert?

Martin M. schrieb:

> In Pascal macht man Mehrzuweisungen mit einem Set of
> http://wiki.freepascal.org/Set

Das ist doch nur Eye-Candy. Letztlich machen diese ganzen 
Mengenoperatoren in Pascal nix anderes als die binären logischen 
Verknüpfungen in C. Der einzige Vorteil der Mengen ist, dass sie bis zu 
256Bit Breite implementiert sind. Wenn die Quelle der Raubkopie aber 
C-Code ist, spielt dieser Vorteil keine Rolle...

Und, fast noch wichtiger: man kann das dann auch in Pascal genauso 
ausdrücken wie in C, den Eye-Candy-Quatsch der Mengenoperatoren also 
einfach komplett außen vor lassen, denn Pascal kennt genau dieselben 
binär-logischen Operatoren wie C. Sie heißen halt nur anders...

von Rolf M. (rmagnus)


Lesenswert?

Dr. Sommer schrieb:
> Martin M. schrieb:
>> I2S_Mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
>
> Lieber einen named cast nutzen:I2S_Mode =
> static_cast<i2s_mode_t>(I2S_MODE_MASTER | I2S_MODE_TX);
> Dann ist klarer was passiert, und man kann weniger falsch machen.

In einem is2_mode_t diesen Wert zu speichern ist schon etwas, das falsch 
gemacht wurde. Wenn man da irgendwelche verODERungen der Enumeratoren 
speichern will, muss IS2Mode einfach vom Typ int statt i2s_mode_t sein. 
Dann braucht man auch keinen hässlichen Cast.

von Dr. Sommer (Gast)


Lesenswert?

Rolf M. schrieb:
> In einem is2_mode_t diesen Wert zu speichern ist schon etwas, das falsch
> gemacht wurde. Wenn man da irgendwelche verODERungen der Enumeratoren
> speichern will, muss IS2Mode einfach vom Typ int statt i2s_mode_t sein.

Ansichtssache. Bitfelder in enums zu speichern ist erlaubt und absolut 
üblich. Ein Parameter "i2s_mode_t mode" ist deutlich sprechender als ein 
"int mode". Einen vorzeichenbehafteten Integer sollte man auch nicht 
dafür nehmen. Besser ist natürlich ein "enum class" mit entsprechend 
überladenen Operatoren...

von Rolf M. (rmagnus)


Lesenswert?

Dr. Sommer schrieb:
> Ansichtssache. Bitfelder in enums zu speichern ist erlaubt und absolut
> üblich.

Erlaubt ist es eben nicht (in C++). Deshalb muss man ja mit einem Cast 
den Compiler dazu zwingen, es trotzdem zu akzeptieren.
"I2S_MODE_MASTER | I2S_MODE_TX" ist vom Typ int, daher würde ich auch 
das Ergebnis in einem int speichern.

> Ein Parameter "i2s_mode_t mode" ist deutlich sprechender als ein
> "int mode".

Dafür könnte man sich ja zur Not noch einen Typedef basteln.

> Einen vorzeichenbehafteten Integer sollte man auch nicht dafür nehmen.

Ok, für Bitgefummel nimmt man besser unsigned. Da gebe ich dir recht.

> Besser ist natürlich ein "enum class" mit entsprechend überladenen
> Operatoren...

Ja.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Rolf M. schrieb:
> Erlaubt ist es eben nicht (in C++).

Sicher? Wo im Standard steht das?

Rolf M. schrieb:
> Warum nicht? Enumeratoren dürfen auch negativ sein.
Darf schon. Das Vorzeichenbit hat aber Sonderfunktionen, die nicht 
portabel sind. Wenn man z.B. anfängt damit zu shiften könnte es Probleme 
geben. Ob ein 16-bit-int den Wert -32768 darstellen kann ist auch nicht 
garantiert; ein 16-bit-"unsigned int" kann aber garantiert alle 16 
Bitmasken 1,2,4, ... 32768 ablegen.

von Rolf M. (rmagnus)


Lesenswert?

Dr. Sommer schrieb:
> Rolf M. schrieb:
>> Erlaubt ist es eben nicht (in C++).
>
> Sicher? Wo im Standard steht das?

Naja, grundsätzlich erlaubt ist es per expliziter Konvertierung, solange 
der Bereich der enum-Werte nicht verlassen wird. Schön finde ich es aber 
nicht. Und dass man dafür einen Cast braucht, zeigt auch für mich schon, 
dass es auch nicht so gedacht ist.

von Bernd K. (prof7bit)


Lesenswert?

c-hater schrieb:
> Und, fast noch wichtiger: man kann das dann auch in Pascal genauso
> ausdrücken wie in C, den Eye-Candy-Quatsch der Mengenoperatoren also
> einfach komplett außen vor lassen

Das ist kein Eye-Candy-Quatsch, das ist Typsicherheit und schön sauber 
hinzuschreiben und zu lesen. Wenn ein Pascal-Programm endlich kompiliert 
weil alle Typen stimmen und der Compiler nicht mehr meckert kannst Du 
davon ausgehen daß es auch funktioniert und das tut was Du willst. Zwar 
nicht immer aber ungefähr 4 bis 9 mal wahrscheinlicher als bei C.

> man kann das dann auch in Pascal genauso
> ausdrücken wie in C

Lol, niemand würde das ernsthaft wollen. Wozu auch?

: Bearbeitet durch User
von Eric B. (beric)


Lesenswert?

Martin M. schrieb:
> die Orginal Syntax sieht folgendermassen aus
>
>   I2S_Mode  = {
>             .mode = I2S_Mode,

Das geht natürlich nicht...

von Jack (Gast)


Lesenswert?

Bernd K. schrieb:
> Das ist kein Eye-Candy-Quatsch, das ist Typsicherheit und schön sauber
> hinzuschreiben und zu lesen. Wenn ein Pascal-Programm endlich kompiliert
> weil alle Typen stimmen und der Compiler nicht mehr meckert kannst Du
> davon ausgehen daß es auch funktioniert und das tut was Du willst. Zwar
> nicht immer aber ungefähr 4 bis 9 mal wahrscheinlicher als bei C.

Bitte WAS? So einen Spruch habe ich das letzte Mal 1989 gehört, und zwar 
in einem Anfängerkurs den ich gegeben habe "Aber es compiliert, dann 
muss es doch funktionieren".

Wenn du das ernsthaft glaubst, dann ...

von Dr. Sommer (Gast)


Lesenswert?

Jack schrieb:
> Bitte WAS? So einen Spruch habe ich das letzte Mal 1989 gehört, und zwar
> in einem Anfängerkurs den ich gegeben habe "Aber es compiliert, dann
> muss es doch funktionieren".

Dann besuch mal Kurse zu anderen höheren Programmiersprachen, wie Scala 
oder C++. Auch dort wird großer Wert auf Typsicherheit gelegt, da dies 
tatsächlich eine Menge Fehler vermeidet. Wenn nicht alles ein "void*" 
ist und der Compiler prüfen kann ob man kompatible Typen hat, muss man 
das nicht selbst tun. Natürlich bedeutet "kompiliert" hier nicht 
"funktioniert", aber die Wahrscheinlichkeit ist einfach größer.

von Bernd K. (prof7bit)


Lesenswert?

Jack schrieb:
> Bitte WAS?

Ja.

Es ist namlich nicht hauptsächlich der Fakt daß zufällig beim 
Kompilieren die Typen geprüft werden. Es ist vielmehr die Art wie Du in 
so einer Sprache programmierst, wie die Sprache Dir angewöhnt oder Dich 
geradezu zwingt Dir von Anfang an eingehende Gedanken und weitreichende 
Planung der Datentypen (Datenstrukturen) zu machen so daß Du oft schon 
bergeweise ausgeklügelte Typdeklarationen hast die genau zusammenpassen 
und den Gegenstand des Problems abbilden bevor Du auch nur eine 
einzige Zeile ausführbaren Code geschrieben hast. Und das bisschen 
Code das Du dann noch schreibst kann nicht mehr viel anders machen als 
die Puzzlestücke in der einzig erlaubten Weise in der sie zusammenpassen 
zu verwenden. Oft ist nach dem Ersinnen der Typen der Drops schon 
gelutscht und der Rest fügt sich ganz von selbst.

In C kann man einfach drauf los hacken und erst später nachdenken und 
das macht den Unterschied.

: Bearbeitet durch User
von leider C (Gast)


Lesenswert?

Martin M. schrieb:
> In Pascal macht man Mehrzuweisungen mit einem Set of
> http://wiki.freepascal.org/Set
>
> wie macht man das in C

 Gar nicht. C kennt keine Mengentypen. C ist auch schlicht in Bezug auf 
enum. In C ist enum nur eine konstante. Es wird implizit wild hin und 
her gecastet.

An den Antworten kannst du schön ablesen, wie die Cler ticken. :-(

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.