Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should enum never be used in an API?

I am using a C library provided to me already compiled. I have limited information on the compiler, version, options, etc., used when compiling the library. The library interface uses enum both in structures that are passed and directly as passed parameters.

The question is: how can I assure or establish that when I compile code to use the provided library, that my compiler will use the same size for those enums? If it does not, the structures won't line up, and the parameter passing may be messed up, e.g. long vs. int.

My concern stems from the C99 standard, which states that the enum 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.

As far as I can tell, so long as the largest value fits, the compiler can pick any type it darn well pleases, effectively on a whim, potentially varying not only between compilers, but different versions of the same compiler and/or compiler options. It could pick 1, 2, 4, or 8-byte representations, resulting in potential incompatibilities in both structures and parameter passing. (It could also pick signed or unsigned, but I don't see a mechanism for that being a problem in this context.)

Am I missing something here? If I am not missing something, does this mean that enum should never be used in an API?

Update:

Yes, I was missing something. While the language specification doesn't help here, as noted by @Barmar the Application Binary Interface (ABI) does. Or if it doesn't, then the ABI is deficient. The ABI for my system indeed specifies that an enum must be a signed four-byte integer. If a compiler does not obey that, then it is a bug. Given a complete ABI and compliant compilers, enum can be used safely in an API.

like image 528
Mark Adler Avatar asked Dec 28 '13 00:12

Mark Adler


People also ask

Should your API use enums?

In general, use enums only when they will never change, or when they are used for input and can be more resistant to a change in the list of valid values. For fields that change often, avoid enums in favor of a string combined with an operation to obtain the latest set of values with descriptions.

Why enums should not be used?

The problem with enums is described in Fowler's Refactoring, where it is considered a code smell. It has nothing to do with type safety, but rather that it forces you to sprinkle switch statements all over your code, thus violating the DRY Principle.

When should enum be used?

You should use enum types any time you need to represent a fixed set of constants. That includes natural enum types such as the planets in our solar system and data sets where you know all possible values at compile time—for example, the choices on a menu, command line flags, and so on.

Why we should not use enum in Java?

If you ever need to change the value of a String constant, it is a one-line change. With an enum it can get more complicated due to having separate values for name and toString, and those values possibly being used in conditional logic.


2 Answers

APIs that use enum are depending on the assumption that the compiler will be consistent, i.e. given the same enum declaration, it will always choose the same underlying type.

While the language standard doesn't specifically require this, it would be quite perverse for a compiler to do anything else.

Furthermore, all compilers for a particular OS need to be consistent with the OS's ABI. Otherwise, you would have far more problems, such as the library using 64-bit int while the caller uses 32-bit int. Ideally, the ABI should constrain the representation of enums, to ensure compatibility.

More generally, the language specification only ensures compatibility between programs compiled with the same implementation. The ABI ensures compatibility between programs compiled with different implementations.

like image 159
Barmar Avatar answered Oct 21 '22 13:10

Barmar


From the question:

The ABI for my system indeed specifies that an enum must be a signed four-byte integer. If a compiler does not obey that, then it is a bug.

I'm surprised about that. I suspect in reality you're compiler will select a 64-bit (8 byte) size for your enum if you define an enumerated constant with a value larger that 2^32.

On my platforms (MinGW gcc 4.6.2 targeting x86 and gcc 4,.4 on Linux targeting x86_64), the following code says that I get both 4 and 8 byte enums:

#include <stdio.h>

enum { a } foo;
enum { b = 0x123456789 } bar;

int main(void) {
    printf("%lu\n", sizeof(foo));
    printf("%lu", sizeof(bar));   
    return 0;
}

I compiled with -Wall -std=c99 switches.

I guess you could say that this is a compiler bug. But the alternatives of removing support for enumerated constants larger than 2^32 or always using 8-byte enums both seem undesirable.

Given that these common versions of GCC don't provide a fixed size enum, I think the only safe action in general is to not use enums in APIs.

Further notes for GCC

Compiling with "-pedantic" causes the following warnings to be generated:

main.c:4:8: warning: integer constant is too large for 'long' type [-Wlong-long]
main.c:4:12: warning: ISO C restricts enumerator values to range of 'int' [-pedantic]

The behavior can be tailored via the --short-enums and --no-short-enums switches.

Results with Visual Studio

Compiling the above code with VS 2008 x86 causes the following warnings:

warning C4341: 'b' : signed value is out of range for enum constant
warning C4309: 'initializing' : truncation of constant value

And with VS 2013 x86 and x64, just:

warning C4309: 'initializing' : truncation of constant value
like image 30
Andrew Bainbridge Avatar answered Oct 21 '22 15:10

Andrew Bainbridge