Считать строки исходного файла, используя макросы? - PullRequest
15 голосов
/ 08 января 2020

Возможно ли с помощью препроцессора C / C ++ подсчитать строки в исходном файле либо в макросе, либо в некотором виде, доступном во время компиляции? Например, я могу заменить MAGIC1, MAGIC2 и MAGIC3 в следующем, и получить значение 4 каким-либо образом при использовании MAGIC3?

MAGIC1 // can be placed wherever you like before the relevant 
       // lines - either right before them, or in global scope etc.
foo(); MAGIC2
bar(); MAGIC2
baz(); MAGIC2
quux(); MAGIC2
// ... possibly a bunch of code here; not guaranteed to be in same scope ...
MAGIC3

Примечания:

  • Специфичные для компилятора c расширения возможностей препроцессора допустимы, но нежелательны.
  • Если это возможно только с помощью некоторого C ++, в отличие от C, конструкция, это также приемлемо, но нежелательно (т.е. я хотел бы что-то, что будет работать для C).
  • Очевидно, что это можно сделать, запустив исходный файл через некоторый скрипт внешнего процессора, но я не об этом спрашиваю.

Ответы [ 5 ]

15 голосов
/ 08 января 2020

Существует макрос препроцессора __LINE__, который дает вам целое число для строки, в которой отображается строка. Вы можете взять его значение в какой-то строке, а затем в более поздней строке и сравнить.

static const int BEFORE = __LINE__;
foo();
bar();
baz();
quux();
static const int AFTER = __LINE__;
static const int COUNT = AFTER - BEFORE - 1; // 4

Если вы хотите посчитать вхождения чего-то, а не строки источника, __COUNTER__ может быть нестандартным опция, поддерживаемая некоторыми компиляторами, такими как G CC и MSV C.

#define MAGIC2_2(c)
#define MAGIC2(c) MAGIC2_2(c)
static const int BEFORE = __COUNTER__;
void foo(); MAGIC2(__COUNTER__);
void bar(
    int multiple,
    float lines); MAGIC2(__COUNTER__);
void baz(); MAGIC2(__COUNTER__);
void quux(); MAGIC2(__COUNTER__);
static const int AFTER = __COUNTER__;
static const int COUNT = AFTER - BEFORE - 1; // 4

Я принял начальное значение __COUNTER__, поскольку оно могло использоваться ранее в исходный файл или некоторый включенный заголовок.

В C вместо C ++ существуют ограничения на постоянные переменные, поэтому вместо него можно использовать enum.

enum MyEnum
{
    FOO = COUNT // C: error: enumerator value for ‘FOO’ is not an integer constant
};

Замена const с enum:

enum {BEFORE = __LINE__};
foo();
bar();
baz();
quux();
enum { COUNT = __LINE__ - BEFORE - 1};
enum MyEnum
{
    FOO = COUNT // OK
};
9 голосов
/ 08 января 2020

Я знаю, что запрос OP - использовать макросы, но я хотел бы добавить еще один способ сделать это, не связанный с использованием макросов.

C ++ 20 представляет класс source_location, который представляет определенная информация об исходном коде, такая как имена файлов, номера строк и имена функций. Мы можем использовать это довольно легко в этом случае.

#include <iostream>
#include <source_location>

static constexpr auto line_number_start = std::source_location::current().line();
void foo();
void bar();
static constexpr auto line_number_end = std::source_location::current().line();

int main() {
    std::cout << line_number_end - line_number_start - 1 << std::endl; // 2

    return 0;
}

И живой пример здесь .

7 голосов
/ 08 января 2020

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

#define CONCATENATE(s1, s2) s1##s2
#define EXPAND_THEN_CONCATENATE(s1, s2) CONCATENATE(s1, s2)

#define COUNT_THIS_LINE static_assert(__COUNTER__ + 1, "");
#define START_COUNTING_LINES(count_name) \
  enum { EXPAND_THEN_CONCATENATE(count_name, _start) = __COUNTER__ };
#define FINISH_COUNTING_LINES(count_name) \
  enum { count_name = __COUNTER__ - EXPAND_THEN_CONCATENATE(count_name, _start) - 1 };

Это скрывает детали реализации (хотя он скрывает их внутри макросов ...). Это обобщение ответа @ MaxLanghof. Обратите внимание, что __COUNTER__ может иметь ненулевое значение, когда мы начинаем подсчет.

Вот как это используется:

START_COUNTING_LINES(ze_count)

int hello(int x) {
    x++;
    /* some */     COUNT_THIS_LINE
    void source(); COUNT_THIS_LINE
    void lines();  COUNT_THIS_LINE
    return x;
}

FINISH_COUNTING_LINES(ze_count)

int main()
{
    return ze_count;
}

Кроме того, это действительно C - если ваш препроцессор поддерживает __COUNTER__, то есть.

Работает на GodBolt .

Если вы используете C ++, вы можете изменить это решение, чтобы даже не загрязнять глобальное пространство имен - поместив счетчики в namespace macro_based_line_counts { ... } или namespace detail et c. )

7 голосов
/ 08 января 2020

Для полноты: если вы хотите добавить MAGIC2 после каждой строки, вы можете использовать __COUNTER__:

#define MAGIC2 static_assert(__COUNTER__ + 1, "");

/* some */     MAGIC2
void source(); MAGIC2
void lines();  MAGIC2

constexpr int numberOfLines = __COUNTER__;

int main()
{
    return numberOfLines;
}

https://godbolt.org/z/i8fDLx (возвращает 3 )

Вы можете сделать его многоразовым, сохранив начальное и конечное значения __COUNTER__.

В целом это действительно громоздко. Вы также не сможете считать строки, содержащие директивы препроцессора или заканчивающиеся комментариями //. Я бы использовал __LINE__ вместо этого, см. Другой ответ.

5 голосов
/ 08 января 2020

На основании вашего комментария, если вы хотите указать размер массива (во время компиляции) в C или C ++, вы можете сделать

int array[]; //incomplete type
enum{ LINE0 = __LINE__ }; //enum constants are integer constant expressions
/*lines to be counted; may use array (though not sizeof(array)) */
/*...*/
int array[ __LINE__ - LINE0 ]; //complete the definition of int array[]

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

int array[]; static int count;
enum{ LINE0 = __LINE__ }; //enum constants are integer constant expressions
//... possibly use count here
enum { LINEDIFF = __LINE__ - LINE0 }; 
int array[ LINEDIFF ]; /*complete the definition of int array[]*/ 
static int count = LINEDIFF; //fill the count in later

Решение на основе __COUNTER__ (если это расширение доступно) в отличие от решения на основе __LINE__ будет работать так же.

constexpr s в C ++ должен работать так же, как enum, но enum будет работать и в обычном C (мое решение выше - простое C решение).

...