Порахувати рядки вихідного файлу за допомогою макросів?


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).
  • Очевидно, що це можна зробити, запустивши вихідний файл через якийсь зовнішній сценарій процесора, але це не те, що я прошу.

6
Є макрос, який називається__LINE__ поточним номером рядка
ForceBru

2
Шукаєте __COUNTER__та / або BOOST_PP_COUNTER?
KamilCuk

11
Яку актуальну проблему потрібно вирішити? Навіщо вам це потрібно?
Якийсь програміст чувак

1
Чи допомагає це ?
користувач1810087

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

Заміна const на 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 ++]. Для C enum константи працюють.
Вогнем улан,

9

Я знаю, що запит ОП полягає у використанні макросів, але я хотів би додати інший спосіб цього, який не передбачає використання макросів.

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бути експериментальним у С ++ 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
@ lastlyknownas_463035818 __COUNTER__самостійно не буде дозволено, і його потрібно розширити на щось, або це не буде рахувати (я не можу запам'ятати правила на 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т.д.)


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 рішення).


Це буде працювати лише в тому випадку, якщо моє використання підрахунку рядків знаходиться в тому ж обсязі, що і підрахунок рядків. IIANM. Примітка. Я трохи відредагував своє запитання, щоб підкреслити, що це може бути проблемою.
einpoklum

1
@einpoklum У __COUNTER__базі рішення є і такі проблеми: ви краще сподіваєтесь, що ваш магічний макрос є єдиним користувачем __COUNTER__, принаймні до того, як ви закінчите з вашим використанням __COUNTER__. Проблема, в основному, зводиться до простих фактів, які __COUNTER__/__LINE__є функціями препроцесора, і препроцесор працює за один прохід, тому ви не можете зробити повторне виправлення цілого константного вираження пізніше на основі __COUNTER__/ __LINE__. Єдиний спосіб (щонайменше, на C) - уникнути потреби в першу чергу, наприклад, шляхом використання оголошень масиву вперед без розміру (неповно набрані декларації масиву).
PSkocik

1
Для запису \ значення не впливає __LINE__- якщо є розрив рядка, __LINE__збільшується. Приклад 1 , приклад 2 .
Макс Лангхоф

@MaxLanghof Дякую Не усвідомлював цього. Виправлено.
PSkocik
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.