Проверьте, является ли созданная константа - PullRequest
1 голос
/ 19 марта 2019

Я пытаюсь создать тест, который проверяет, определяет ли определенный файл защиту заголовка с определенным пространством имен.Поскольку тест является общим, это пространство имен известно только во время компиляции и передается как -DTHENAMESPACE=BLA.Затем мы используем некоторую магию из https://stackoverflow.com/a/1489985/1711232, чтобы вставить это вместе.

Это означает, что я хочу сделать что-то вроде:

#define PASTER(x, y) x##_##y
#define EVALUATOR(x, y) PASTER(x, y)
#define NAMESPACE(fun) EVALUATOR(THENAMESPACE, fun)
#ifndef NAMESPACE(API_H)  // evaluates to BLA_API_H
#  error "namespace not properly defined"
#endif

Но это не работает должным образом, с cppжаловаться на то, что ifndef не ожидает скобок.

Как я могу сделать это правильно, если это вообще возможно?

Я также попытался добавить дополнительные слои косвенного обращения, но не сбольшой успех.


Таким образом, при правильном выполнении #ifdef это, по крайней мере, кажется невозможным:

Учитывая оператор defined:

Если определенный оператор появляется в результате расширения макроса, стандарт C говорит, что поведение не определено .GNU cpp рассматривает его как подлинно определенный оператор и оценивает его как обычно.Он будет предупреждать везде, где ваш код использует эту функцию, если вы используете параметр командной строки -Wpedantic, так как другие компиляторы могут обрабатывать это по-другому.Предупреждение также активируется -Wextra, а также может быть активировано индивидуально с -Wexpansion-to-define.

https://gcc.gnu.org/onlinedocs/cpp/Defined.html#Defined

, а ifdef ожидает MACRO, идальнейшее расширение не выполняется.

https://gcc.gnu.org/onlinedocs/cpp/Ifdef.html#Ifdef

Но, возможно, можно вызвать предупреждение «неопределенная константа» (-Wundef), которое также позволило бы моему тестовому конвейеру перехватить этопроблема.

Ответы [ 4 ]

5 голосов
/ 19 марта 2019

Если мы предположим, что include guard всегда выглядит как

#define NAME /* no more tokens here */

и , если, как вы сказали, допустима любая ошибка времени компиляции (а не только #error), тоВы можете сделать следующее:

#define THENAMESPACE BLA
#define BLA_API_H // Comment out to get a error.

#define CAT(x,y) CAT_(x,y)
#define CAT_(x,y) x##y

#define NAMESPACE(x) static int CAT(UNUSED_,__LINE__) = CAT(CAT(THENAMESPACE,CAT(_,x)),+1);

NAMESPACE(API_H)

Здесь NAMESPACE(API_H) пытается объединить BLA_API_H и +, используя ##.

Это приводит к error: pasting "BLA_API_H" and "+" does not give a valid preprocessing token , за исключением, если BLA_API_H равно #define d для «без токенов».

При наличии #define BLA_API_H, NAMESPACE(API_H) просто становится

static int UNUSED_/*line number here*/ = +1;

Если высоглашаясь на менее надежное решение, вы даже можете получить хорошие сообщения об ошибках:

#define THENAMESPACE BLA
#define BLA_API_H // Comment out to get a error.

#define TRUTHY_VALUE_X 1
#define CAT(x,y) CAT_(x,y)
#define CAT_(x,y) x##y

#define NAMESPACE(x) CAT(CAT(TRUTHY_VALUE_,CAT(THENAMESPACE,CAT(_,x))),X)

#if !NAMESPACE(API_H)
#error "namespace not properly defined"
#endif

Здесь, если определено BLA_API_H, то #if !NAMESPACE(API_H) расширяется до #if 1.

ЕслиBLA_API_H не определено, затем оно расширяется до #if TRUTHY_VALUE_BLA_API_HX, а TRUTHY_VALUE_BLA_API_HX оценивается как false из-за неопределенности.

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

3 голосов
/ 19 марта 2019

Я не думаю, что расширение макроса внутри директивы #ifndef возможно в области стандарта C.

#ifndef symbol эквивалентно #if !defined symbol. Стандарт говорит об этом (§6.10.1 Условное включение):

  1. ... может содержать выражения унарных операторов вида

    defined identifier
    

    или

    defined ( identifier )
    

и

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

Таким образом, в основном идентификаторы в выражении defined не раскрываются, и ваш текущий NAMESPACE(API_H) является недопустимой формой идентификатора.

Возможным обходным путем может быть простое использование:

#if NAMESPACE(API_H) == 0
#  error "namespace not properly defined"
#endif

Это работает, потому что несуществующие идентификаторы заменяются на 0. Проблема с этим подходом заключается в том, что будет ложная ошибка, если BLA_API_H определено как 0, но в зависимости от вашей ситуации это может быть приемлемо.

1 голос
/ 19 марта 2019

Вы также можете сделать это с помощью сопоставления с образцом препроцессора.

#define PASTE3(A,B,C) PASTE3_I(A,B,C)
#define PASTE3_I(A,B,C) A##B##C
#define PASTE(A,B) PASTE_I(A,B)
#define PASTE_I(A,B) A##B
#define THIRD(...) THIRD_I(__VA_ARGS__,,,)
#define THIRD_I(A,B,C,...) C
#define EMPTINESS_DETECTOR ,
#if THIRD(PASTE3(EMPTINESS_,PASTE(THENAMESPACE,_API_H),DETECTOR),0,1)
#  error "namespace not properly defined"
#endif

Это та же идея, что и в ответе @ HolyBlackCat, за исключением того, что это сделано в препроцессоре;внутренняя вставка в выражении в директиве #if генерирует токен на основе THENAMESPACE, вставляя его в требуемый _API_H.Если сам этот токен определен в макросе, он расширится до метки места во время операции PASTE3;это эффективно вставляет EMPTINESS_ [placemarker] DETECTOR, который является макросом, расширяющимся до запятой.Эта запятая сместит аргументы косвенного THIRD, поместив туда 0, сделав условный эквивалент #if 0.Все остальное не будет сдвигать аргументы, что приводит к THIRD выбору 1, что вызывает #error.

Это также делает то же самое предположение, что ответ HolyBlackCat делает ... что охранники включения всегда выглядят как #define BLA_API_H, но вы можете приспособить определенные альтернативные стили, используя расширенное сопоставление с образцом ... например, если вы хотите принять охрану включения, например #define BLAH_API_H 1 (кто это делает?), Вы можете добавить #define EMPTINESS_1DETECTOR ,.

0 голосов
/ 19 марта 2019

Язык не определяет никакой иной способ, кроме использования оператора defined или его эквивалента для проверки того, определены ли идентификаторы как имена макросов.В частности, директива препроцессора вида

#ifndef identifier

эквивалентна

#if ! defined identifier

( C11, 6.10.1 / 5 ).Аналогичное относится к #ifdef.

. Оператор defined принимает в качестве операнда один идентификатор, но не выражение ( C11, 6.10.1 / 1 ).Более того, хотя выражение, связанное с директивой #if, расширяется макросом до оценки, поведение не определено, если в этом контексте расширение макроса создает маркер "defined", а имена макросов, измененные унарным оператором defined,явным образом исключен из раскрытия ( C11, 6.10.1 / 4 ).

Таким образом, хотя во многих контекстах можно создавать имена макросов посредством вставки токенов, и в таких контекстах результатыпосле этого может быть применено расширение макроса, операнд оператора defined не является таким контекстом.Поэтому язык не определяет способ проверки того, определен ли созданный или косвенно указанный идентификатор как имя макроса.

ОДНАКО, вы можете не полагаться на defined, если вы контролируете все средства защиты заголовков,и вы готовы немного отклониться от традиционного стиля.Вместо того, чтобы просто #define использовать заголовки, определите их от до некоторого ненулевого целочисленного значения, скажем, 1:

#if ! MYPREFIX_SOMEHEADER_H
#define MYPREFIX_SOMEHEADER_H 1

// body of someheader.h ...

#endif

Затем вы можете удалить оператор defined из вашего тестового выражения.:

#if ! NAMESPACE(API_H)
#  error "namespace not properly defined"
#endif

Обратите внимание, однако, что директива #define имеет похожую проблему: она определяет один идентификатор, который не подлежит предыдущему расширению макроса.Таким образом, вы не можете динамически создавать имена охранников заголовков.Я не уверен, имел ли ты это в виду, но если ты это сделал, то все предчувствия, вероятно, спорные.

...