¿Contar líneas de archivo fuente usando macros?

15

¿Es posible, utilizando el preprocesador C / C ++, contar líneas dentro de un archivo fuente, ya sea en una macro o en algún tipo de valor disponible en tiempo de compilación? Por ejemplo, ¿puedo reemplazar MAGIC1, MAGIC2y MAGIC3en lo siguiente, y obtener el valor 4 de alguna manera cuando lo uso 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

Notas:

  • Las extensiones específicas del compilador a las capacidades del preprocesador son aceptables pero indeseables.
  • Si esto es posible solo con la ayuda de algunos de C ++, a diferencia de C, construir, eso también es aceptable pero indeseable (es decir, me gustaría algo que funcione para C).
  • Obviamente, esto se puede hacer ejecutando el archivo fuente a través de un script de procesador externo, pero eso no es lo que estoy preguntando.
einpoklum
fuente
66
Hay una macro llamada__LINE__ que representa el número de línea actual
ForceBru
2
¿Estás buscando __COUNTER__y / o BOOST_PP_COUNTER?
KamilCuk
11
¿Cuál es el problema real que necesitas resolver? ¿Por qué necesitas esto?
Algún tipo programador el
1
¿ Esto ayuda?
user1810087
1
@PSkocik: Quiero algo que pueda usar como una constante de tiempo de compilación, por ejemplo, para decir int arr[MAGIC4]y obtener el número de líneas en alguna sección previamente contada de mi código.
Einpoklum

Respuestas:

15

Existe la __LINE__macro de preprocesador que le da un número entero para que aparezca la línea. Puede tomar su valor en alguna línea, y luego en una línea posterior, y comparar.

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

Si desea contar las ocurrencias de algo en lugar de las líneas de origen, __COUNTER__podría ser una opción no estándar, compatible con algunos compiladores como GCC y 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

Tomé el valor inicial de __COUNTER__porque podría haber sido utilizado previamente en el archivo de origen, o algún encabezado incluido.

En C en lugar de C ++, existen limitaciones en las variables constantes, por lo enumque se podría usar una en su lugar.

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

Reemplazando el const con enum:

enum {BEFORE = __LINE__};
foo();
bar();
baz();
quux();
enum { COUNT = __LINE__ - BEFORE - 1};
enum MyEnum
{
    FOO = COUNT // OK
};
Lancer de fuego
fuente
Creo que esto es lo más cercano que puede obtener con solo el preprocesador. El preprocesador es de una sola pasada, por lo que no puede hacer un backpatch de un valor calculado posteriormente, pero las referencias de variables globales funcionarán y deberían optimizarlo. Simplemente no funcionarán en expresiones de constantes enteras, pero podría ser posible estructurar el código para que no sean necesarias para los recuentos.
PSkocik
2
__COUNTER__no es estándar en C o C ++. Si sabe que funciona con compiladores particulares, especifíquelos.
Peter
@einpoklum no, BEFOREy AFTERno son macros
Alan Birtles
Hay un problema con la versión sin contador de esta solución: el antes y el después solo se pueden usar en el mismo alcance que las líneas de origen. Edité mi fragmento "eg" para reflejar que este es un problema.
Einpoklum
1
@ user694733 La verdadera pregunta fue etiquetada [C ++]. Para C enum constantes de trabajo.
Fire Lancer
9

Sé que la solicitud del OP es utilizar macros, pero me gustaría agregar otra forma de hacerlo que no implique el uso de macros.

C ++ 20 presenta la source_locationclase que representa cierta información sobre el código fuente, como nombres de archivos, números de línea y nombres de funciones. Podemos usar eso con bastante facilidad en este caso.

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

Y vivir ejemplo aquí .

Cascanueces
fuente
Sin macros es incluso mejor que con macros. Sin embargo, con este enfoque, solo puedo usar el recuento de líneas en el mismo alcance que las líneas que he contado. Además, ¿con source_locationser experimental en C ++ 20?
Einpoklum
Estoy de acuerdo en que la solución sin macros es mucho mejor que con macros. source_locationahora es oficialmente parte de C ++ 20. Mira aquí . Simplemente no pude encontrar la versión del compilador gcc en godbolt.org que ya lo admite en sentido no experimental. ¿Puede explicar un poco más su afirmación: solo puedo usar el recuento de líneas en el mismo alcance que las líneas que he contado ?
Cascanueces
Supongamos que pongo su sugerencia dentro de una función (es decir, las líneas contadas son invocaciones, no declaraciones). Funciona, pero solo tengo line_number_starty line_number_enddentro de ese alcance, en ningún otro lugar. Si lo quiero en otro lugar, necesito pasarlo en tiempo de ejecución, lo que frustra el propósito.
Einpoklum
Eche un vistazo al ejemplo que proporciona el estándar aquí . Si es un argumento predeterminado, todavía es parte del tiempo de compilación, ¿verdad?
Cascanueces
Sí, pero eso no se hace line_number_endvisible en tiempo de compilación fuera de su alcance. Corrígeme si estoy equivocado.
Einpoklum
7

Para completar: si está dispuesto a agregar MAGIC2después de cada línea, puede usar __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 (devoluciones 3)

Puede hacerlo reutilizable almacenando los valores inicial y final de __COUNTER__.

En general, esto es realmente engorroso. Tampoco podrá contar líneas que contengan directivas de preprocesador o que terminen con //comentarios. En su __LINE__lugar, usaría , ver la otra respuesta.

Max Langhof
fuente
1
¿Por qué se utiliza static_assert?
idclev 463035818
1
Esto dio "9" en el archivo fuente en el que lo coloqué, no puede asumir __COUNTER__que todavía es cero inicialmente, ya que otros encabezados, etc. podrían usarlo.
Fire Lancer
tienes que usar el valor de __COUNTER__dos veces y tomar la diferencia
idclev 463035818
1
@ formerlyknownas_463035818 __COUNTER__por sí solo no se permitiría, y debe expandirse a algo o no contará (no puedo recordar las reglas al 100% en esto).
Fire Lancer
7

Una solución algo más robusta, que permite diferentes contadores (siempre que no se entremezclen y no sirvan __COUNTER__para otras tareas):

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

Esto oculta los detalles de implementación (aunque los oculta dentro de las macros ...). Es una generalización de la respuesta de @ MaxLanghof. Tenga en cuenta que __COUNTER__puede tener un valor distinto de cero cuando comenzamos un conteo.

Así es como se usa:

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

Además, esto es válido C, si su preprocesador es compatible __COUNTER__, eso es.

Funciona en GodBolt .

Si está utilizando C ++, puede modificar esta solución para ni siquiera contaminar el espacio de nombres global, colocando los contadores dentro namespace macro_based_line_counts { ... }, o namespace detailetc.)

einpoklum
fuente
5

Según su comentario, si desea especificar un tamaño de matriz (tiempo de compilación) en C o C ++, puede hacer

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[]

Si necesita sizeof(array)en las líneas intermedias, puede reemplazarlo con una referencia de variable estática (a menos que sea absolutamente necesario que sea una expresión constante entera) y un compilador optimizador debe tratarlo de la misma manera (eliminar la necesidad de colocar la variable estática) en memoria)

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

Una __COUNTER__solución basada en (si esa extensión está disponible) en lugar de una __LINE__basada en la base funcionará igual.

constexprs en C ++ debería funcionar tan bien como enum, pero enumtambién funcionará en C simple (mi solución anterior es una solución de C simple).

PSkocik
fuente
Esto funcionará solo si mi uso del recuento de líneas está en el mismo alcance que las líneas contadas. IIANM. Tenga en cuenta que edité mi pregunta ligeramente para enfatizar que podría ser un problema.
Einpoklum
1
@einpoklum Una __COUNTER__solución basada también tiene problemas: es mejor que espere que su macro mágica sea el único usuario __COUNTER__, al menos antes de que termine de usar __COUNTER__. Básicamente, el problema se reduce a los hechos simples que __COUNTER__/__LINE__son características del preprocesador y el preprocesador funciona en una sola pasada, por lo que no puede realizar un backpatch de una expresión constante entera más tarde basada en __COUNTER__/ __LINE__. La única forma (al menos en C) es evitar la necesidad en primer lugar, por ejemplo, mediante el uso de declaraciones de matriz directas sin tamaño (declaraciones de matriz con tipo incompleto).
PSkocik
1
Para el registro, el \ no afecta __LINE__: si hay un salto de línea, __LINE__aumenta. Ejemplo 1 , ejemplo 2 .
Max Langhof
@MaxLanghof Gracias. No me di cuenta de eso. Fijo.
PSkocik