Убедитесь, что функция C, принимающая перечисление, всегда вызывается с другим аргументом (во время компиляции)

#c #preprocessor #compile-time

Вопрос:

Предположим, я хочу убедиться, что функция всегда вызывается с другим значением параметра, во время компиляции.

Идеально было бы, чтобы это компилировалось:

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

Но это не удается:

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

Итак, мы должны иметь возможность вызывать некоторую функцию foo(en_A) только один раз (то же самое для foo(en_B)).

Менее предпочтительным, но также приемлемым было бы определить foo_en_A, foo_en_B и убедиться, что каждый из них вызывается только один раз.

Возможно ли сделать что-то подобное во время компиляции на C?

Комментарии:

1. Почему именно вы хотите это сделать? Из-за существования таких вещей, как циклы и функции-оболочки, кажется, что это не даст никаких значимых гарантий.

2. Я не могу представить какой-либо способ сделать это во время компиляции.

3. Я не вижу никакого механизма в C, который позволил бы такое. Вы можете просто скомпилировать разные файлы C и связать их вместе. Как компилятор сможет обнаруживать вызовы в других файлах C? Если вам действительно нужно такое ограничение, вам нужно будет обработать его во время выполнения.

4. Что, если кто-то записывает void wrap_foo_en_A(void) { foo(en_A); } , а затем выполняет wrap_foo_en_A(); в двух разных местах?

5. Типичный способ передачи идентифицирующей информации в процедуру ведения журнала — это написать макрос, который передает __FILE__ и / или __func__ и __LINE__ в процедуру ведения журнала.

Ответ №1:

Возможно ли сделать что-то подобное во время компиляции на C?

Нет, это невозможно. Не только языку программирования C не хватает отражения — он не может проверять себя, но и язык программирования C компилирует по одной «единице перевода» за раз. Хотя с помощью расширений компилятора частично возможно реализовать эту проверку в одном TU, вам придется предоставить специальный компоновщик или плагин компоновщика для реализации этого в нескольких TU.

Чтобы делать то, что вы хотите во время компиляции, вы должны использовать внешние инструменты, помимо самого исходного кода C. Эти инструменты могут работать с сгенерированной программой, проверяя сборку, или могут работать с самим исходным кодом C.

(Вы задаете вопрос XY).

убедитесь, что пользователи библиотеки ведения журнала никогда не делают вызов неоднозначным (например, устанавливают одну и ту же ошибку из двух разных мест), чтобы это место в коде, которое вызвало ошибку, можно было проследить неоднозначно

Для этого просто войдите __FILE__ __LINE__ в систему и наймите нормальных программистов, которые никогда не помещали бы неуникальные сообщения журнала в одну строку или компилировали разные файлы с одинаковым путем.

В любом случае, есть и другой способ решения проблемы. Вместо того, чтобы требовать от программиста вводить уникальный номер по всей базе кода, просто сгенерируйте их числа. Однажды я работал в небольшой встраиваемой системе с очень низкими коммуникационными возможностями, порядка байтов в час. Инструмент, написанный в оболочке с awk , будет сканировать всю базу кода на наличие именно этой строки UNIQUE() , и каждый такой вызов будет заменен уникальным номером по всей базе исходного кода, а затем скомпилирован. Таким образом, вместо того, чтобы «требовать от программистов наличия уникальных номеров», числа генерируются сами по себе, что намного проще запрограммировать, чем проверять, был ли такой-то номер уже использован всеми вашими коллегами.

Комментарии:

1. Стоит 2, если бы это было возможно.

Ответ №2:

Я подозреваю, что предварительная обработка в целом затруднена по тем же причинам, что и проблема остановки. Практически, если вы должны были запустить эту систему без NDEBUG этого, это приведет assert к тому, что функция не будет вызываться с одним и тем же вводом дважды.

 #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)] amp;= ~BITMASK(b))
#define BITTEST(a, b) ((a)[BITSLOT(b)] amp; 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)
        amp;amp; (BITSET(debug_en, en), 1));
}

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

Это приводит к сбойному прерыванию утверждения на втором foo(en_A) . Однако это не тестирование во время компиляции.