Home > database >  Make sure C function accepting enum is always called with a different argument (compile-time)
Make sure C function accepting enum is always called with a different argument (compile-time)

Time:10-28

Suppose I want to make sure that function is always called with a different parameter value, compile-time.

Ideal would be so that this compiles:

enum en {
   en_A,
   en_B
};
...
foo(en_A);

But this fails:

enum en {
   en_A,
   en_B
};
...
foo(en_A);
...
foo(en_A);

So, we should be able to call some function foo(en_A) only once (same for foo(en_B)).

Less preferable, but also acceptable, would be defining foo_en_A, foo_en_B and making sure each of them is called only once.

Is it possible to do something like that compile-time in C?

CodePudding user response:

Is it possible to do something like that compile-time in C?

No it is not possible. Not only C programming language lacks reflection - can't inspect itself, but C programming language compiles one "translation unit" at a time. While it may be with compiler extensions partially possible to implement that check in one TU, you would have to provide a special linker or linker plugin to implement that across multiple TUis.

To do what you want at compile time, you have to use external tools, outside of the C source code itself. These tools could work on the generated program, checking the assembly, or could work on the C source code itself.

(You are asking XY question).

make sure that users of the logging library never make the call ambiguous (e.g. set the same error from two different places), so that place in the code which produced the error can be umambiguously traced

To do that, just log __FILE__ and __LINE__ and hire sane programmers that would never put non-unique log messages on the same line, or compile different files with the same path.

Anyway, there's also another way of tackling the problem. Instead of requiring the programmer to type a unique number across the whole code base, just generate them numbers. I once worked in a bare-metal small embedded system that had very low communication capabilities, in the order of bytes per hour. A tool written in shell with awk would scan the whole code base for exactly the string UNIQUE() and each and every such call would be replaced by a unique number across the whole source code base and then compiled. So instead of "requiring programmers to have unique numbers", the numbers generated themselves, which is way easier to program than to check if such and such number has been already used by all your colleagues.

CodePudding user response:

I suspect it is difficult to pre-process in general for the same reasons that the Halting problem is difficult. Practically, if you were to run this system without NDEBUG, this will assert that the function is not called with the same input twice.

#include <stdio.h>
#include <limits.h>
#include <assert.h>

/* http://c-faq.com/misc/bitsets.html */
#define BITMASK(b) (1 << ((b) % CHAR_BIT))
#define BITSLOT(b) ((b) / CHAR_BIT)
#define BITSET(a, b) ((a)[BITSLOT(b)] |= BITMASK(b))
#define BITCLEAR(a, b) ((a)[BITSLOT(b)] &= ~BITMASK(b))
#define BITTEST(a, b) ((a)[BITSLOT(b)] & BITMASK(b))
#define BITNSLOTS(nb) ((nb   CHAR_BIT - 1) / CHAR_BIT)

enum en {
    en_A,
    en_B,
    en_NUM
};

static unsigned char debug_en[BITNSLOTS(en_NUM)];

static void foo(const enum en en) {
    assert(!BITTEST(debug_en, en)
        && (BITSET(debug_en, en), 1));
}

int main(void) {
    foo(en_A);
    foo(en_B);
    foo(en_A);
    return 0;
}

This gives an assertion failed abort on the second foo(en_A). However, it isn't compile-time testing.

  • Related