Assuring unique values in set of #define statements

Viewed 72

I have a set of tasks with different priorities. I manage the priorities as #define statements in a .h file:

#define PRIO_TASK_A 1
#define PRIO_TASK_B 2
#define PRIO_TASK_C 3
// ...

As the list of tasks is rather long, making sure that no two tasks have the same priority is prone to error. What is the best way to check for uniqueness? E.g. that the following does not happen: PRIO_TASK_A = 4 and PRIO_TASK_B = 4.

2 Answers

Here is a macro solution! Disclaimer, I don't think its clean, it relies on the __COUNTER__ macro (which is gcc specific), the problem sounds like its better handled by an enum, and it would likely mess up any other area of your program that uses the __COUNTER__ macro.

Here is a sample program with some explanation.


//  macro for using compile time checks

//// The "_H" Helper macro defines a unique type with the priority of the task baked into the typename
//// The __COUNTER__ macro makes this typedef name a different type from all other SA_UNIQUE_PRIO_CALLS
//// We need a helper macro so that the VAL argument can be expanded to its int literal value
#define SA_UNIQUE_PRIO_H(VAL) typedef char static_assertion_uniq_prio_##VAL[__COUNTER__]

//// This macro is what should be called - guarantees that all asserts to it at compile time take a different value
#define STATIC_ASSERT_UNIQUE_PRIO(VAL) SA_UNIQUE_PRIO_H(VAL)

// \ end macro


// Define your priorities here
#define PRIO_TASK_A 1
#define PRIO_TASK_B 2
#define PRIO_TASK_C 3
#define PRIO_TASK_D 1


// check for uniqueness here.
STATIC_ASSERT_UNIQUE_PRIO(PRIO_TASK_A);
STATIC_ASSERT_UNIQUE_PRIO(PRIO_TASK_B);
STATIC_ASSERT_UNIQUE_PRIO(PRIO_TASK_C);
STATIC_ASSERT_UNIQUE_PRIO(PRIO_TASK_D);

int main() {
    return 0;
}

Note- I think you would have to put the STATIC_ASSERT_UNIQUE_PRIO calls in a c file, since if it is included in multiple files, then there will be conflicting typedefs.

You could use XMacro to manage constants. This approach lets defining all constants in one place. Moreover arbitrary constant expression can be used. The solution works in C99 and above without any compiler specific extensions. The main disadvantage is that a value of all constants must fit to int.

#define PRIO_TASK__XMACRO(X) \
  X(PRIO_TASK_A, 1) \
  X(PRIO_TASK_B, 2) \
  X(PRIO_TASK_C, 3) \
  X(PRIO_TASK_D, 1+1)

Instantiate the constant as a anonymous enum:

#define DECLARE_ENUM(NAME,VALUE) NAME = VALUE,

enum {
PRIO_TASK__XMACRO(DECLARE_ENUM)
};

As suggested in the comment by Jonathan Leffler, use switch to detect if a value was ever repeated.

static inline void check_unique_enums(int c) {
  switch (c) {
#define CASE_ENUM(NAME, VALUE) case NAME: break;
PRIO_TASK__XMACRO(CASE_ENUM)
  }
}

Compiling with gcc produced errors:

so.c: In function ‘check_unique_enums’:
so.c:15:32: error: duplicate case value
   15 | #define CASE_ENUM(NAME, VALUE) case NAME: break;
      |                                ^~~~
so.c:5:3: note: in expansion of macro ‘CASE_ENUM’
    5 |   X(PRIO_TASK_D, 1+1)
      |   ^
so.c:16:1: note: in expansion of macro ‘PRIO_TASK__XMACRO’
   16 | PRIO_TASK__XMACRO(CASE_ENUM)
      | ^~~~~~~~~~~~~~~~~
so.c:15:32: note: previously used here
   15 | #define CASE_ENUM(NAME, VALUE) case NAME: break;
      |                                ^~~~
so.c:3:3: note: in expansion of macro ‘CASE_ENUM’
    3 |   X(PRIO_TASK_B, 2) \
      |   ^
so.c:16:1: note: in expansion of macro ‘PRIO_TASK__XMACRO’
   16 | PRIO_TASK__XMACRO(CASE_ENUM)
      | ^~~~~~~~~~~~~~~~~
Related