Contar linhas de arquivos de origem usando macros?

15

É possível, usando o pré-processador C / C ++, contar linhas dentro de um arquivo de origem, em uma macro ou em algum tipo de valor disponível no tempo de compilação? Por exemplo, eu pode substituir MAGIC1, MAGIC2e MAGIC3no seguinte, e obter o valor 4 de alguma forma quando se utiliza 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:

  • Extensões específicas do compilador para os recursos do pré-processador são aceitáveis, mas indesejáveis.
  • Se isso for possível apenas com a ajuda de algumas das construções em C ++, em oposição a C, isso também é aceitável, mas indesejável (ou seja, eu gostaria de algo que funcionasse para C).
  • Obviamente, isso pode ser feito executando o arquivo de origem através de algum script de processador externo, mas não é isso que estou perguntando.
einpoklum
fonte
6
uma macro chamada__LINE__ que representa o número da linha atual
ForceBru
2
Está procurando __COUNTER__e / ou BOOST_PP_COUNTER?
KamilCuk 8/01
11
Qual é o problema real que você precisa resolver? Por que você precisa disso?
Algum programador
11
Será que isso ajuda?
user1810087 8/01
11
@PSkocik: Eu quero algo que eu possa usar como uma constante em tempo de compilação, por exemplo, para dizer int arr[MAGIC4]e obter o número de linhas em alguma seção contada anteriormente do meu código.
einpoklum 8/01

Respostas:

15

Existe a __LINE__macro do pré - processador que fornece um número inteiro para a linha em que é exibida. Você pode pegar seu valor em alguma linha, e depois em outra linha, e comparar.

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

Se você deseja contar as ocorrências de algo em vez de linhas de origem, __COUNTER__pode ser uma opção não padrão, suportada por alguns compiladores, como GCC e 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

Peguei o valor inicial de __COUNTER__porque ele poderia ter sido usado anteriormente no arquivo de origem ou em algum cabeçalho incluído.

Em C, e não em C ++, há limitações em variáveis ​​constantes; portanto, um enumpode ser usado.

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

Substituindo a const por enum:

enum {BEFORE = __LINE__};
foo();
bar();
baz();
quux();
enum { COUNT = __LINE__ - BEFORE - 1};
enum MyEnum
{
    FOO = COUNT // OK
};
Fire Lancer
fonte
Eu acho que isso é o mais próximo possível, com o pré-processador. O pré-processador é de uma passagem, portanto, você não pode voltar atrás com um valor calculado posteriormente, mas as referências de variáveis ​​globais funcionarão e deverão otimizar o mesmo. Eles simplesmente não funcionam com expressões de constantes inteiras, mas pode ser possível estruturar o código para que não sejam necessários para as contagens.
PSkocik
2
__COUNTER__não é padrão em C ou C ++. Se você sabe que funciona com determinados compiladores, especifique-os.
Peter
@einpoklum não, BEFOREe AFTERnão são macros
Alan Birtles
Há um problema com a versão sem contador desta solução: o antes e o depois são utilizáveis ​​apenas no mesmo escopo que as linhas de origem. Editei meu snippet "por exemplo" para refletir que esse é um problema.
einpoklum 8/01
11
@ user694733 A pergunta verdadeira foi marcada com [C ++]. Para constantes C enum, trabalho.
Fire Lancer
9

Sei que a solicitação do OP é usar macros, mas gostaria de adicionar outra maneira de fazer isso que não envolve o uso de macros.

O C ++ 20 apresenta a source_locationclasse que representa certas informações sobre o código-fonte, como nomes de arquivos, números de linhas e nomes de funções. Podemos usar isso com bastante facilidade neste 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;
}

E exemplo ao vivo aqui .

NutCracker
fonte
Sem macros é ainda melhor do que com macros. No entanto - com essa abordagem, só posso usar a contagem de linhas no mesmo escopo que as linhas que contei. Além disso - com source_locationser experimental em C ++ 20?
einpoklum 8/01
Concordo que a solução sem macros é muito melhor do que com macros. source_locationagora faz parte oficialmente do C ++ 20. Confira aqui . Eu simplesmente não consegui encontrar a versão do compilador gcc no godbolt.org que já a suporta no sentido não experimental. Você pode explicar um pouco mais a sua declaração - só posso usar a contagem de linhas no mesmo escopo que as linhas que contei ?
NutCracker
Suponha que eu coloque sua sugestão em uma função (ou seja, as linhas contadas são invocações, não declarações). Funciona - mas eu só tenho line_number_starte line_number_enddentro desse escopo, em nenhum outro lugar. Se eu quiser em outro lugar, preciso passá-lo em tempo de execução - o que anula o objetivo.
einpoklum 8/01
Veja o exemplo que o padrão fornece aqui . Se for um argumento padrão, ainda faz parte do tempo de compilação, certo?
NutCracker 8/01
Sim, mas isso não fica line_number_endvisível em tempo de compilação fora do seu escopo. Corrija-me se eu estiver errado.
einpoklum 8/01
7

Para completar: se você deseja adicionar MAGIC2depois de cada linha, pode 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 (retorna 3)

Você pode torná-lo reutilizável armazenando os valores inicial e final de __COUNTER__.

No geral, isso é realmente complicado. Você também não poderá contar linhas que contêm diretivas de pré-processador ou que terminam com //comentários. Eu usaria em __LINE__vez disso, veja a outra resposta.

Max Langhof
fonte
11
por que você usa static_assert?
idclev 463035818 8/01
11
Isso deu "9" no arquivo de origem em que o soltei, você não pode assumir que __COUNTER__ainda é zero inicialmente, pois outros cabeçalhos etc. podem usá-lo.
Fire Lancer
você tem que usar o valor de __COUNTER__duas vezes e fazer a diferença
idclev 463035818 08/01
11
@ ex __COUNTER__- conhecidoas_463035818 por si só não seria permitido, e ele precisa ser expandido para algo ou não será contado (não lembro as regras 100% sobre isso).
Fire Lancer
7

Uma solução um pouco mais robusta, que permite contadores diferentes (desde que não se misturem e que não sejam utilizados __COUNTER__para outras tarefas):

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

Isso oculta os detalhes da implementação (embora oculte-os dentro de macros ...). É uma generalização da resposta de @ MaxLanghof. Observe que __COUNTER__pode ter um valor diferente de zero quando iniciamos uma contagem.

Veja como é usado:

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

Além disso, isso é C válido - se o seu pré-processador suportar __COUNTER__, é isso.

Funciona no GodBolt .

Se você estiver usando C ++, poderá modificar esta solução para não poluir o namespace global - colocando os contadores dentro namespace macro_based_line_counts { ... }ou namespace detailetc.)

einpoklum
fonte
5

Com base no seu comentário, se você quiser especificar um tamanho de matriz (em tempo de compilação) em C ou C ++, poderá fazer

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

Se você precisar sizeof(array)nas linhas intermediárias, poderá substituí-lo por uma referência de variável estática (a menos que seja absolutamente necessária uma expressão constante inteira) e um compilador de otimização deve tratá-la da mesma forma (eliminar a necessidade da variável estática ser colocada em memória)

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

Uma __COUNTER__solução baseada em A (se essa extensão estiver disponível) em oposição a uma solução baseada em A __LINE__funcionará da mesma maneira.

constexprs em C ++ deve funcionar tão bem quanto enum, mas enumtambém funcionará em C simples (minha solução acima é uma solução C simples).

PSkocik
fonte
Isso funcionará apenas se meu uso da contagem de linhas estiver no mesmo escopo que as linhas contadas. IIANM. Observe que editei minha pergunta um pouco para enfatizar que isso poderia ser um problema.
einpoklum 8/01
11
@einpoklum Uma __COUNTER__solução baseada também tem problemas: é melhor você esperar que sua macro mágica seja o único usuário __COUNTER__, pelo menos antes de terminar de usar __COUNTER__. O problema basicamente se resume aos fatos simples que __COUNTER__/__LINE__são os recursos do pré-processador e o pré-processador funciona de uma só vez; portanto, você não pode retroceder uma expressão constante inteira posteriormente com base em __COUNTER__/ __LINE__. A única maneira (pelo menos em C) é evitar a necessidade em primeiro lugar, por exemplo, usando declarações de matriz direta sem tamanho (declarações de matriz com tipo incompleto).
PSkocik 8/01
11
Para o registro, o \ não afeta __LINE__- se houver uma quebra de linha, __LINE__aumenta. Exemplo 1 , exemplo 2 .
Max Langhof
@MaxLanghof Thanks. Não percebi isso. Fixo.
PSkocik 8/01