开发者

How to make gcc warn about passing wrong enum to a function

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,
   开发者_如何学JAVA REG16_C
}REG16;

void function(REG8 reg8)
{

}

int main(void)
{
    function(REG16_A);    // Should warn about wrong enum
}


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
}


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


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
$


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.)


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.


$ 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)’
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜