Считать строки исходного файла, используя макросы?

15

Возможно ли с помощью препроцессора 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).
  • Очевидно, что это можно сделать, запустив исходный файл через сценарий внешнего процессора, но я не об этом.
einpoklum
источник
6
Там в макрос под названием__LINE__ , которое представляет номер текущей строки
ForceBru
2
Ищите __COUNTER__и / или BOOST_PP_COUNTERищите?
KamilCuk
11
Какова текущая проблема , которую нужно решить? зачем вам это?
Какой-то программист чувак
1
Имеет ли эта помощь?
user1810087
1
@PSkocik: я хочу что-то, что я мог бы использовать в качестве константы времени компиляции, например, для произнесения int arr[MAGIC4]и получения количества строк в некотором ранее подсчитанном разделе моего кода.
einpoklum

Ответы:

15

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

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

Если вы хотите посчитать вхождения чего-то, а не исходные строки, это __COUNTER__может быть нестандартным вариантом, поддерживаемым некоторыми компиляторами, такими как GCC и MSVC.

#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
};

Замена конста с помощью enum:

enum {BEFORE = __LINE__};
foo();
bar();
baz();
quux();
enum { COUNT = __LINE__ - BEFORE - 1};
enum MyEnum
{
    FOO = COUNT // OK
};
Огненный Лансер
источник
Я думаю, что это ближе всего, как вы можете получить только с препроцессором. Препроцессор является однопроходным, поэтому вы не можете создать обратно позднее вычисленное значение, но ссылки на глобальные переменные будут работать и должны оптимизировать то же самое. Они просто не будут работать с выражениями целочисленных констант, но может оказаться возможным структурировать код так, чтобы он не был необходим для подсчета.
PSkocik
2
__COUNTER__не является стандартным в C или C ++. Если вы знаете, что он работает с определенными компиляторами, укажите их.
Питер
@einpoklum нет, BEFOREи AFTERне макросы
Алан Биртлз
Существует проблема с не встречной версией этого решения: до и после могут использоваться только в той же области, что и исходные строки. Отредактировал мой фрагмент "например", чтобы отразить, что это проблема.
einpoklum
1
@ user694733 Истинный вопрос был помечен [C ++]. Для констант enum работают.
Огненный Лансер
9

Я знаю, что запрос 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;
}

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

Щелкунчик
источник
Без макросов это даже лучше, чем с макросами. Однако - при таком подходе я могу использовать количество строк только в той же области, что и подсчитанные мной строки. Кроме того - с source_locationбыть экспериментальным в C ++ 20?
einpoklum
Я согласен, что решение без макросов намного лучше, чем с макросами. source_locationтеперь официально является частью C ++ 20. Проверьте здесь . Я просто не смог найти версию компилятора gcc на godbolt.org, которая уже поддерживает его в неэкспериментальном смысле. Не могли бы вы объяснить немного больше вашего утверждения - я могу использовать количество строк только в той же области, что и подсчитанные мной строки ?
NutCracker
Предположим, я поместил ваше предложение в функцию (т.е. подсчитанные строки являются вызовами, а не объявлениями). Это работает - но у меня есть только line_number_startи line_number_endв этой области, нигде больше. Если я хочу это в другом месте, мне нужно передать его во время выполнения - что побеждает цель.
einpoklum
Посмотрите на пример, который стандарт предоставляет здесь . Если это аргумент по умолчанию, то это все еще часть времени компиляции, верно?
NutCracker
Да, но это не делает line_number_endвидимым во время компиляции вне его контекста. Поправьте меня если я ошибаюсь.
einpoklum
7

Для полноты: если вы хотите добавить 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__вместо этого, увидеть другой ответ.

Макс Лангоф
источник
1
почему ты используешь static_assert?
idclev 463035818
1
Это дало «9» в исходном файле, в который я его поместил, вы не можете предположить, __COUNTER__что изначально он все еще равен нулю, поскольку другие заголовки и т. Д. Могут его использовать.
Огненный Лансер
Вы должны использовать значение __COUNTER__дважды и взять разницу
idclev 463035818
1
Сама __COUNTER__по себе @ прежняя известная_463035818 не будет допущена, и ей нужно что-то расширять, иначе это не будет учитываться (я не могу запомнить правила на 100%).
Огненный Лансер
7

Несколько более надежное решение, позволяющее использовать разные счетчики (если они не смешиваются и не используются __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т. Д.)

einpoklum
источник
5

Исходя из вашего комментария, если вы хотите указать размер массива (во время компиляции) в 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)промежуточные строки, вы можете заменить его ссылкой на статическую переменную (если это не обязательно должно быть целочисленное константное выражение), а оптимизирующий компилятор должен обрабатывать его точно так же (исключая необходимость размещения статической переменной в памяти)

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__основе будет работать так же.

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

PSkocik
источник
Это будет работать, только если я использую счетчик строк в той же области, что и подсчитанные строки. IIANM. Заметьте, я немного отредактировал свой вопрос, чтобы подчеркнуть, что это может быть проблемой.
einpoklum
1
@einpoklum Решение на __COUNTER__основе также имеет проблемы: вам лучше надеяться, что ваш волшебный макрос - единственный пользователь __COUNTER__, по крайней мере, до того, как вы закончили с его использованием __COUNTER__. Проблема в основном сводится к простым фактам, которые __COUNTER__/__LINE__являются особенностями препроцессора, и препроцессор работает за один проход, поэтому вы не можете потом добавить целочисленное константное выражение на основе __COUNTER__/ __LINE__. Единственный способ (по крайней мере, в C) - это избежать необходимости, например, используя объявления массива вперед без размера (объявления не полностью типизированного массива).
PSkocik
1
На запись \ это не влияет __LINE__- если есть разрыв строки, __LINE__увеличивается. Пример 1 , пример 2 .
Макс Лангоф
@MaxLanghof Спасибо. Не понял этого. Исправлена.
PSkocik