9

In particular, i got following code in library interface:

typedef enum
{
    state1,
    state2,
    state3,
    state4,
    state5,
    state_error = -1,
} State;

I strictly forbidden to break ABI. However, I want to add state6 and state7. Will it break ABI?

I found here some tip, but i somewhat doubt if it`s my case?

You can...

  • append new enumerators to an existing enum.

Exeption: if that leads to the compiler choosing a larger underlying type for the enum,that makes the change binary-incompatible. Unfortunately, compilers have some leeway to choose the underlying type, so from an API-design perspective it's recommended to add a Max.... enumerator with an explicit large value (=255, =1<<15, etc) to create an interval of numeric enumerator values that is guaranteed to fit into the chosen underlying type, whatever that may be.

Community
  • 1
  • 1
Jurasic
  • 1,845
  • 1
  • 23
  • 29
  • 2
    `state3` has three different codes? – Deduplicator Dec 04 '14 at 17:57
  • 2
    Adding a new enum while leaving existing ones alone shouldn't break ABI compatibility as long as the size of the underlying type doesn't change as a result of the additions. Which is what your quoted material says. – Jonathan Leffler Dec 04 '14 at 17:58

3 Answers3

16

Your question is a nice example why long-term maintaining of ABI compatibility is a difficult task. The core of the problem here is that the compatibility depends not just on the given type, but also on how it is used in function/method prototypes or complex types (e.g. structures, unions etc.).

(1) If the enumeration is used strictly as an input into the library (e.g. as a parameter of a function which just changes of behavior the function/library), then it keeps the compatibility: You changed the contract in a way which can never hurt the customer i.e. the calling application. Old applications shall never use the new value and will get the old behavior, new applications just get more options.

(2) If the enumeration is used anywhere as an output from the library (e.g. return value or function filling some address provided by caller aka an output parameter), the change would break the ABI. Consider the enumeration to be a contract saying "the application never sees values other then those listed". Adding new enum member would break this contract because old applications could now see values they never counted with.

That is at least, if there are no measures to protect old applications from falling into these troubles. Generally speaking, the library still can output the new value, but never for any valid input potentially provided by the old applications.

There are some design patterns allowing such enum expansions:

E.g. the library can provide an initialization function which allows to specify version of ABI the application is ready for. Old application ask for version 1.0 and never get the new value on input; newer application specify 1.1. or 2.0 or if the new enum value as added in the version 1.1, and then it may get the new value.)

Or, if a function DoSomething() is getting some flags on input, you may add a new flag where application can specify it's ready to see the new output value.

Or, if that's not possible, new version of the library may add a new function DoSomethingEx() which provides the more complex behavior than the original DoSomething(). DoSomethingEx() now can return the new enum value, the DoSomething() cannot.

As a side note if you ever need to add such DoSomethingEx(), do it in a way that allows similar expansions in the future. For consistency, it's usually a good idea to design it so that DoSomethingEx() with default flags (usually zero) behaves the same way DoSomething() and only with some new flag(s) it offers a different and more complex behavior.

Drawback of course is that the library implementation has to check what the application is ready for and provide a behavior compatible for expectations of old applications. It does not seem as much but over time and many versions of the library, there may be dozens of such checks accumulated in the library implementation, making it more complex and harder to maintain.

mity
  • 2,299
  • 17
  • 20
  • Put another way, one may weaken the libraries requirements without harm, but one may not weaken its guarantees. Beware of people relying on parameter-validation to reject invalid input in a specific way though. Those exist... – Deduplicator Dec 04 '14 at 18:06
  • can `sizeof State` not change? Will it be the same after adding new states? Or when can we not rely on the size beeing the same? After going higher than 127? – 12431234123412341234123 Jul 22 '21 at 15:21
  • In theory, yes. In practice, I think have never seen so large enums. Furthermore, if you want/need to be super safe, you should actually never use enumerations in headers which are part of an ABI: The C standard allows compilers to choose the underlying integer type as they see fit. For example, afaik `gcc` always uses `int` as long as all values can fit into it. Another compiler could use `char` when seeing exactly the same `enum` declaration and all the values can fit into it. – mity Jul 23 '21 at 20:16
3

The quote actually is your case. Simple add the new enum values at the end (but before the state_error as it has a different value) and it should be binary compatible, unless, as mentioned in the quote you provided, the compiler chooses to use a different sized type, which doesn't seem likely in the case of such a small enum.

The best way is to try and check: a simple sizeof(State) executed before and after the changes should be enough (though you also might want to check if the values are still the same).

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Kelm
  • 967
  • 5
  • 14
2

Take a look at the highest-valued enumerator-id: state3 is 2.

That means, even if the compiler should have chosen char as the underlying type, you can comfortably fit 100+ additional enumerator-ids there, without risking damage to binary compatibility.

That pre-supposes that the users supply a value of the iterator, instead of ever reading one, though.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118