使用宏计算源文件行?


15

使用C / C ++预处理程序,是否可以将源文件中的行计数为宏或某种编译时可用的值?例如,我可以在下面替换MAGIC1MAGIC2MAGIC3在使用时以某种方式获得值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
请问帮助?
user1810087

1
@PSkocik:我想要一些可以用作编译时常量的东西,例如用于说出int arr[MAGIC4]并获取代码中先前计数的部分中的行数。
einpoklum

Answers:


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可以改用an 。

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没有,BEFOREAFTER不是宏
艾伦Birtles

该解决方案的非计数器版本存在一个问题:之前和之后只能在与源代码行相同的范围内使用。编辑了我的“ eg”片段以反映出这是一个问题。
einpoklum

1
@ user694733真实的问题被标记为[C ++]。对于C枚举常量起作用。
Fire Lancer

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的一部分。在这里检查。我只是在godbolt.org找不到已经支持非实验意义的gcc编译器版本。您能否再多解释一下您的陈述- 我只能在与已计数行相同的范围内使用行计数
NutCracker

假设我将您的建议放在一个函数中(即计数的行是调用,而不是声明)。它的工作原理-而我有的只是line_number_startline_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__其他标头起初仍为零,等等。
Fire Lancer

您必须使用__COUNTER__两倍的值并取差
idclev 463035818

1
@ formerlyknownas_463035818 __COUNTER__本身是不允许的,它需要扩展为某种内容,否则将不起作用(我不记得规则100%)。
Fire Lancer

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__相同的。

constexprC ++中的s应该和一样好enum,但enum也可以在普通C中工作(我上面的解决方案是普通C解决方案)。


仅当我使用的行数与所计数的行在同一范围内时,此方法才有效。IIANM。请注意,我稍微编辑了我的问题以强调这可能是一个问题。
einpoklum

1
@einpoklum一个__COUNTER__基于解决方案的问题也同样存在:您最好希望魔术宏是的唯一用户__COUNTER__,至少在使用完之前__COUNTER__。问题基本上都归结为__COUNTER__/__LINE__具有预处理器功能的简单事实,并且预处理器可以一次通过,因此以后不能基于__COUNTER__/ 对整数常量表达式进行后修补__LINE__。唯一的方法(至少在C语言中)是首先避免需要,例如,通过使用不带大小的前向数组声明(类型不完整的数组声明)。
PSkocik

1
记录下来,\ 不会影响__LINE__-如果出现换行符,则会__LINE__增加。示例1示例2
Max Langhof

@MaxLanghof谢谢。没意识到。固定。
PSkocik
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.