24

gcc doesn't seem to produce a warning with the following code. How can I get it to produce a warning?

typedef enum
{
    REG8_A,
    REG8_B,
    REG8_C
}REG8;

typedef enum
{
    REG16_A,
    REG16_B,
    REG16_C
}REG16;

void function(REG8 reg8)
{

}

int main(void)
{
    function(REG16_A);    // Should warn about wrong enum
}
Rocketmagnet
  • 5,656
  • 8
  • 36
  • 47
  • What GCC version do you have? GCC 4.4.5 installed on my machine rejects the code with appropriate error message. See my answer: http://stackoverflow.com/questions/4669454/how-to-make-gcc-warn-about-passing-wrong-enum-to-a-function/4713303#4713303 – ulidtko Jan 17 '11 at 12:49
  • The version is 3.4.4. And, sadly, there is no option of changing this. – Rocketmagnet Jan 17 '11 at 13:27

6 Answers6

12

For a way to do this in C using GCC's -Wenum-compare (which is enabled by default if you enable -Wall), you must perform a comparison on the enumeration constant before you pass it to the function in order to get the desired diagnostic.

-Wenum-compare

Warn about a comparison between values of different enumerated types. In C++ enumeral mismatches in conditional expressions are also diagnosed and the warning is enabled by default. In C this warning is enabled by -Wall.

http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html

For such a comparison happen to happen automatically when we call the function, we can wrap the function in a macro. For readability I also define a macro SAFE_ENUM that performs a harmless comparison on the enumeration constant (this is what ultimately triggers the warning when trying to pass the wrong enumeration constant to foo or bar).

/**
  SAFE_ENUM: evaluate an enumeration constant safely
  TYPE: the enumeration type
  VAL: the enumeration constant to evaluate
*/
#define SAFE_ENUM(TYPE, VAL) ((VAL) == (TYPE)0 ? (VAL) : (VAL))

typedef enum
{
    REG8_DEFAULT,
    REG8_A,
    REG8_B,
    REG8_C
} Reg8;

typedef enum
{
    REG16_DEFAULT,
    REG16_A,
    REG16_B,
    REG16_C
} Reg16;

void foo(Reg8 reg8)
#define foo(reg8) foo(SAFE_ENUM(Reg8, reg8))
{
    printf("%s called with value %d\n", __func__, reg8);
}

void bar(Reg16 reg16)
#define bar(reg16) bar(SAFE_ENUM(Reg16, reg16))
{
    printf("%s called with value %d\n", __func__, reg16);
}

int main(void)
{
    foo(REG8_A);  // ok
    bar(REG16_A); // ok
    foo(REG16_B); // warning
    bar(REG8_B);  // warning

    Reg16 a_reg16 = 42;
    foo(a_reg16); // warning: foo requires a Reg8 but you gave it a Reg16
}
Brandin
  • 906
  • 11
  • 20
  • 2
    +1 because this is a nice idea, unfortunately it does not work when used with enum variables, this will compile without warning: `Reg16 reg = 42; bar(reg);` – Étienne Nov 05 '14 at 17:19
  • @Étienne Thanks for the comment. However variables work fine by my testing -- that is, bar(reg) should be OK according to the example. bar wants a Reg16 and you gave it exactly a Reg16 when you call bar(reg). Try foo(reg) and it will produce a warning because reg is Reg16 but foo wants a Reg8. – Brandin Nov 05 '14 at 22:05
  • 1
    What I meant is that your solution answers the question perfectly and is probably the best one can get in C, but unfortunately the enum is still not really safe as the macro name implies, at least it is still less safe than for example a C++ enum. One could e.g. do `Reg16 reg = REG8_A; bar(reg);`. – Étienne Nov 05 '14 at 23:01
8

The reason of such behaviour is that you are using C compiler rather than C++. And in C enum types are not really types, enums in C just hold int constants and they can be freely mixed with whatever integers and whatever arithmetic.

In C++ instead you have real enums as you would like to think of them, and the needed typechecking occurs as conveyed by the language standard.

Your problem can be resolved in two ways:

  • Use C++ compiler.

    This way you will have real enums, as you want them.

  • Change your code to be in pure-C style, i.e. not using enums, as in C they are merely constant sets where the compiler only helps you to order the constant values. And in C you will be the one responsible to keep the "types" of passed constants consistent. Once again: for C, enum members are just int constants, you can't make them typed.


#define REG8_A 0
#define REG8_B 1
#define REG8_C 2

#define REG16_A 0
#define REG16_B 1
#define REG16_C 2
Community
  • 1
  • 1
ulidtko
  • 14,740
  • 10
  • 56
  • 88
  • I would *love* to use C++ instead of C. But that's not an option here sadly. But I think I will continue to use enums, because at least it will be more clear when reading the code. – Rocketmagnet Jan 17 '11 at 13:21
  • 1
    You might try mark specific `REGn_` "types" with some high bit raised on. For example, `enum REG8 { REG8_A=0x80, REG8_B, REG8_C};` and make the bits be different for different enums. This way you could add runtime checks (asserts) via `value & 0x80` to your functions, which could "check the type". – ulidtko Jan 17 '11 at 13:26
  • Got the same issue with ObjC here. I guess it works the same way as C here. Using Apple LLVM 5.0. – Jonny Jan 14 '14 at 02:28
  • 1
    gcc does not print all warnings a C compiler should do. At bugs@gcc they told me that the devs have to care about other things. Sounds like they have not enough people power. clang 3.5 prints a warnings, like this: warning: implicit conversion from enumeration type 'blu_t' to different enumeration type 'bla_t' [-Wenum-conversion] – rockdaboot Jun 05 '15 at 13:13
  • One could imagine using a C++ compiler to check C source code (purely as a sort of continuous sanity check). This does require extra work, though (e.g., see http://stackoverflow.com/questions/861517), which may or may not be worth it in your situation. – rkersh Apr 13 '16 at 22:01
  • @RyanKersh C11 (and maybe even as far as C99) is no longer a strict subset of C++. I.e. there's perfectly legal C code that cannot be compiled as C++. – ulidtko Apr 14 '16 at 08:07
  • 2
    Clang warns (`-Wenum-conversion`), and this flag has been added to gcc trunk (non-default) so I believe you can expect it to work in gcc-10. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78736#c14 – Jed Jan 28 '20 at 22:54
6

The only way I can see of generating a warning is if you are prepared to pass pointers rather than bare enums, e.g.

typedef enum
{
    REG8_A,
    REG8_B,
    REG8_C
} REG8;

typedef enum
{
    REG16_A,
    REG16_B,
    REG16_C
} REG16;

void function(REG8 * reg8)
{

}

int main(void)
{
    REG16 r = REG16_A;
    function(&r);
    return 0;
}

Not exactly an elegant solution, but it does give a warning, at least with gcc -Wall:

$ gcc -Wall warn_enum.c -o warn_enum
warn_enum.c: In function ‘main’:
warn_enum.c:23: warning: passing argument 1 of ‘function’ from incompatible pointer type
$
Paul R
  • 208,748
  • 37
  • 389
  • 560
  • 1
    I tried this, but it didn't seem to produce a warning either. – Rocketmagnet Jan 17 '11 at 14:21
  • @Rocketmagnet: were you compiling with `-Wall` ? (see above) – Paul R Jan 17 '11 at 14:37
  • @Rocketmagent: OK - I see from another comment that you're using a very old version of gcc, so that may explain why you don't see the warnings. gcc 4.0 onwards should give the above warning. – Paul R Jan 17 '11 at 16:36
  • Do you know any reason GCC implements warnings for `enum` pointers but not `enum` values? Or does the `-Wenum-compare` option work both ways, *i.e.*, what the OP was looking for? – J. C. Salomon Jan 19 '11 at 21:30
2

As others have pointed out, C does not differentiate between an enumerated type and the underlying integer type. (Some compilers might include type-checking for enums or typedefs as extensions; YMMV.)

To get type-checking in C, you can use structs, but then you lose use of the built-in comparison operators and the ability to switch on a variable. You might try something like this, though:

typedef struct {
    enum {
        reg8_val_A,
        reg8_val_B,
        reg8_val_C,
    } val;
} reg8;
#define reg8_A (reg8){.val = reg8_val_A}
#define reg8_B (reg8){.val = reg8_val_B}
#define reg8_C (reg8){.val = reg8_val_C}
…
bool
is_A_or_B(reg8 reg) {
    if reg.val == reg8_A.val    // one way to compare
        return true;
    switch (reg.val) {
        case reg8_val_B:        // the other way to compare; note that
            return true;        // “case reg8_B.val:” will *not* work
        case reg8_val_C:
            return false;
        default:
            fprintf(stderr, "bad reg value %d\n", reg.val);
            abort();
    }
}

(Uses some C99 features.)

J. C. Salomon
  • 4,143
  • 2
  • 29
  • 38
2

With GCC 4.6 you should use -Wconversion and -Werror options to prevent any implicit type conversions. It gives an error with code posted by Paul R. But original code compiles fine anyway. I don't know why.

Gosha U.
  • 623
  • 1
  • 4
  • 16
0
$ g++ test3.cpp -o test3
test3.cpp: In function ‘int main()’:
test3.cpp:22: error: cannot convert ‘REG16’ to ‘REG8’ for argument ‘1’ to ‘void function(REG8)’
ulidtko
  • 14,740
  • 10
  • 56
  • 88
  • g̶c̶c̶ ̶p̶r̶o̶d̶u̶c̶e̶s̶ ̶t̶h̶e̶ ̶s̶a̶m̶e̶ ̶m̶e̶s̶s̶a̶g̶e̶.̶ When I rename `test3.cpp` to `test3.c`, yes, it compiles without a warning. – ulidtko Jan 17 '11 at 12:55
  • 2
    Sadly, I cannot compile it as C++, it must be compiled as C. – Rocketmagnet Jan 17 '11 at 13:04
  • Still there is a chance that GCC offers some extension to C compiler which allows this. Search is needed. – ulidtko Jan 17 '11 at 13:13