2

I was under the impression that it was an error or at least a warning for an integer type to be passed into a function that requires an enum argument. But I tried it in Compiler Explorer with -Wall -Wextra -Werror and there are no warnings. What am I missing? Does the C standard allow this?

Perhaps more importantly to my immediate use, is there a compiler option for gcc or clang to warn for this? The intent of having an enum type as a formal argument is as a contract to restrict to valid enum values.

#include <stdint.h>

typedef enum {
    FLAG_A = 1,
    FLAG_B = 2,
    FLAG_C = 4
} FLAG;

uint16_t flags = 0;

void clearFlags(FLAG flag)
{
    flags &= ~flag;
}

void doit()
{
    clearFlags(flags);   // this should not be ok; flags is type uint16_t
}
Jason S
  • 184,598
  • 164
  • 608
  • 970
  • 1
    @ason S Enumerations in C are integer types. – Vlad from Moscow Aug 24 '20 at 17:06
  • Read [n1570](https://web.cs.dal.ca/~vlado/pl/C_Standard_2011-n1570.pdf). I believe it is legal in C11 (not in C++). – Basile Starynkevitch Aug 24 '20 at 17:06
  • 2
    Still some compilers do opt to warn about "mixing enum with integer" types – Eugene Sh. Aug 24 '20 at 17:08
  • which compilers? is there a way I can turn that warning on in gcc or clang? – Jason S Aug 24 '20 at 17:39
  • related (but not identical) to https://stackoverflow.com/questions/4669454/how-to-make-gcc-warn-about-passing-wrong-enum-to-a-function – Jason S Aug 24 '20 at 17:43
  • looks like clang has [`-Wassign-enum`](https://clang.llvm.org/docs/DiagnosticsReference.html#wassign-enum) – Jason S Aug 24 '20 at 17:54
  • for [gcc](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html) I only see `-Wenum-compare` and `-Wenum-conversion` and `-Wswitch-enum` – Jason S Aug 24 '20 at 17:57
  • Imagine you had warnings about "from uint16_t to an enum type". Likely then code would likewise warn with `~flag` as than goes from `FLAG` to `int/unsigned`. – chux - Reinstate Monica Aug 24 '20 at 20:13
  • "Imagine you had warnings about "from uint16_t to an enum type" -- yes, because `~flag` is not a valid `enum` value. The C compiler has no idea whether I'm working with bitwise values or not. If I really want to do that math, I should explicitly cast to the enum value, and it's my responsibility to make sure that the resulting values are valid. In that case I should use `flags &= ~(uint16_t)flag;` – Jason S Aug 24 '20 at 22:00

2 Answers2

3

Enumerated types are compatible with integer types, so it is valid to pass an integer to a function that expects an enum as an argument.

Section 6.7.2.2p4 of the C standard regarding Enumeration Specifiers states:

Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, but shall be capable of
representing the values of all the members of the
enumeration. The enumerated type is incomplete until immediately after the } that terminates the list of enumerator declarations, and complete thereafter.

The definition of compatible types is in section 6.2.7p1:

Two types have compatible type if their types are the same.

So this means that an enumerated type actually has the same type as one of the integer types.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • !!!! Even if the value passed in is not a valid value within the `enum`?! – Jason S Aug 24 '20 at 17:32
  • 1
    @JasonS: Nothing restricts `enum`s to contain only the values given labels. In fact, it is quite common to use an `enum` to list flag masks, with the expectation that the actual value will be the bitwise-or of one or more of the labelled values. – rici Aug 24 '20 at 18:00
  • so is the key word "compatible"? what are the implications of "compatible" in the C standard? – Jason S Aug 24 '20 at 18:01
  • @rici then what is the point of a separate type `enum MyEnum` if it's just going to act like one of the integer types? If I have a function `void doSomething(enum MyEnum e);` why should I bother if `e` can be any arbitrary integer value of the "compatible type"? – Jason S Aug 24 '20 at 22:02
  • @jasonS: that's a reasonable question for which I have no answer. But, then, I also have no responsibility; it was not I who designed C some 40-odd years ago. You could use C++, which lets you define enum classes closer to your ideal. All I can say is, that's not the way C works, it's been like that since roughly forever, and it ain't gonna change now. – rici Aug 24 '20 at 22:29
  • (However, I do prefer using `enum` to define my constants instead of `#define`. Although nothing forces me to do so.) – rici Aug 24 '20 at 22:30
  • @JasonS: I don't know who you are asking about the word "compatible". In case it's me, the answer is that if two types are compatible, then an object can be declared with one of those types in one translation unit and the other one in a different translation unit. Since enum, struct and union types do not have external linkage, one of those types defined in some translation union is not "the same type" as a similar-looking type in another translation unit; compatibility defines the circumstances under which they can interoperate. Compatibility rules make enum's possible to use between TUs. – rici Aug 24 '20 at 22:43
2

It's legal (as already explained in other answers). The compiler may (but is not required to) warn:

A value is given to an object of an enumerated type other than by assignment of an enumeration constant that is a member of that type, or an enumeration object that has the same type, or the value of a function that returns the same enumerated type

— Annex I, Common warnings.

hobbs
  • 223,387
  • 19
  • 210
  • 288