1 of 41

Compilação em detalhes

1

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

2 of 41

O processo de compilação

Programas C++ são divididos entre fontes e headers. Vamos entender o papel de cada um deles?

Qual a sequência de eventos disparada sob o seguinte comando?

2

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

3 of 41

O processo de compilação

  1. O pré-processador incorpora #includes, expande macros e resolve diretivas condicionais.

Programas C++ são divididos entre fontes e headers. Vamos entender o papel de cada um deles.

Qual a sequência de eventos disparada sob o seguinte comando?

3

Onde está o gargalo dessa sequência?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

4 of 41

O processo de compilação

  • O pré-processador incorpora #includes, expande macros e resolve diretivas condicionais.
  • O compilador realiza análise léxica, sintática e semântica, produzindo código assembly.

Programas C++ são divididos entre fontes e headers. Vamos entender o papel de cada um deles.

Qual a sequência de eventos disparada sob o seguinte comando?

4

Onde está o gargalo dessa sequência?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

5 of 41

O processo de compilação

  • O pré-processador incorpora #includes, expande macros e resolve diretivas condicionais.
  • O compilador realiza análise léxica, sintática e semântica, produzindo código assembly.
  • O assembler gera um object file com código dependente de posição (relocatable).

Programas C++ são divididos entre fontes e headers. Vamos entender o papel de cada um deles.

Qual a sequência de eventos disparada sob o seguinte comando?

5

Onde está o gargalo dessa sequência?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

6 of 41

O processo de compilação

  • O pré-processador incorpora #includes, expande macros e resolve diretivas condicionais.
  • O compilador realiza análise léxica, sintática e semântica, produzindo código assembly.
  • O assembler gera um object file com código dependente de posição (relocatable).
  • O linker atribui posições, substitui nomes e os combina tudo em um binário (e.g. um executável).

Programas C++ são divididos entre fontes e headers. Vamos entender o papel de cada um deles.

Qual a sequência de eventos disparada sob o seguinte comando?

6

Onde está o gargalo dessa sequência?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

7 of 41

Famigerado #include

O pré-processador é uma tecnologia arcaica. Ao encontrar um #include, ele realiza uma cópia textual do arquivo indicado. Originalmente era visto como uma ferramenta de reutilização de código. Hoje se tornou um pesadelo para os compiladores.

7

gcc -E -P program.cpp

?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

8 of 41

Famigerado #include

O pré-processador é uma tecnologia arcaica. Ao encontrar um #include, ele realiza uma cópia textual do arquivo indicado. Originalmente era visto como uma ferramenta de reutilização de código. Hoje se tornou um pesadelo para os compiladores.

Uma forward declaration teria resolvido o problema nesse caso?

8

gcc -E -P program.cpp

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

9 of 41

Consequências...

As mesmas definições, de classes ou funções, são compiladas inúmeras vezes.

Por quê o compilador não pode salvar o resultado da compilação e reutilizá-lo?

9

code_generator.cpp

optimizer.cpp

register.h

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

10 of 41

A regra da definição única

A One-Definition Rule (ODR) determina que uma única definição de determinada classe, função, template, etc. deve existir no programa. Mas, antes de tentar entendê-la em sua plenitude, damos um passo atrás para esclarecer uma confusão comum relacionada com diretivas de compilação.

  1. O seguinte programa é composto, unicamente, pelo fonte p.cpp e pelo header a.h.

10

p.cpp

a.h

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

11 of 41

A regra da definição única

  1. O seguinte programa é composto, unicamente, pelo fonte p.cpp e pelo header a.h.
  2. O projeto cresce e, eventualmente, adicionamos um novo header, b.h.

11

p.cpp

a.h

b.h

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

12 of 41

A regra da definição única

  1. O seguinte programa é composto, unicamente, pelo fonte p.cpp e pelo header a.h.
  2. O projeto cresce e, eventualmente, adicionamos um novo header, b.h.
  3. Tudo corria bem, até o momento em que b.h também precisa incluir a.h.

12

p.cpp

a.h

b.h

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

13 of 41

A regra da definição única

  1. O seguinte programa é composto, unicamente, pelo fonte p.cpp e pelo header a.h.
  2. O projeto cresce e, eventualmente, adicionamos um novo header, b.h.
  3. Tudo corria bem, até o momento em que b.h também precisa incluir a.h.
  4. Felizmente, nos lembramos de proteger os headers com diretivas #ifndef. Tudo volta a funcionar!

13

p.cpp

a.h

b.h

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

14 of 41

A regra da definição única

  1. O seguinte programa é composto, unicamente, pelo fonte p.cpp e pelo header a.h.
  2. O projeto cresce e, eventualmente, adicionamos um novo header, b.h.
  3. Tudo corria bem, até o momento em que b.h também precisa incluir a.h.
  4. Felizmente, nos lembramos de proteger os headers com diretivas #ifndef. Tudo volta a funcionar!
  5. Mas a alegria dura pouco... Com o novo fonte q.cpp, o programa está novamente quebrado.

O que é __Z10violateODRv?

14

p.cpp

a.h

b.h

q.cpp

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

15 of 41

A regra da definição única

Recapitulando: A One-Definition Rule (ODR) determina que uma única definição de determinada classe, função, template, etc. deve existir no programa.

  • O problema que tivemos na etapa 3 é um resultado de compilação.

Conseguimos resolvê-lo através de diretivas #ifdef, as quais protegem contra o risco de múltiplas definições dentro de uma única unidade de tradução (translation unit).

15

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

16 of 41

Consequências...

As mesmas definições, de classes ou funções, são compiladas inúmeras vezes.

Apesar do linker utilizar apenas uma delas!

16

code_generator.cpp

optimizer.cpp

register.h

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

17 of 41

A regra da definição única

Recapitulando: A One-Definition Rule (ODR) determina que uma única definição de determinada classe, função, template, etc. deve existir no programa.

  • O problema que tivemos na etapa 3 é um resultado de compilação.

Conseguimos resolvê-lo através de diretivas #ifdef, as quais protegem contra o risco de múltiplas definições dentro de uma única unidade de tradução (translation unit).

  • O problema que tivemos na etapa 5 é um resultado de ligação.

Ele acontece pois a ODR protege contra o risco de múltiplas definições em um programa completo.

17

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

18 of 41

ODR: cuidado para não se confundir

18

Vários fontes pode fazer #include de um mesmo header.

Arquivos headers podem conter definições de classes.

Logo, podem existir classes duplicadas em fontes diferentes.

Por quê não acontece um erro de ODR nesse caso?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

19 of 41

Os "segredos" da ODR

Enquanto que a essência da ODR é clara, os detalhes de sua especificação não são triviais. Em particular, o significado de unicidade relativo a uma entidade é o seguinte.

19

  • São consideradas únicas classes, templates ou funções inline que:
  • Apareçam em translation units diferentes;
  • Sejam sintaticamente, token-a-token, equivalentes;
  • Possuam a mesma semântica.

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

20 of 41

Os "segredos" da ODR

  • OK, que faça valer a regra. Mas por quê o tratamento especial para essas entidades?

Enquanto que a essência da ODR é clara, os detalhes de sua especificação não são triviais. Em particular, o significado de unicidade relativo a uma entidade é o seguinte.

Como reproduzir uma classe token-a-token, mas com outra semântica?

20

  • São consideradas únicas classes, templates ou funções inline que:
  • Apareçam em translation units diferentes;
  • Sejam sintaticamente, token-a-token, equivalentes;
  • Possuam a mesma semântica.

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

21 of 41

Os "segredos" da ODR

Enquanto que a essência da ODR é clara, os detalhes de sua especificação não são triviais. Em particular, o significado de unicidade relativo a uma entidade é o seguinte.

Classes

O compilador precisa saber o tamanho (em bytes) da classe. Só assim é possível calcular o espaço ocupado por suas instâncias em memória.

21

  • São consideradas únicas classes, templates ou funções inline que:
  • Apareçam em translation units diferentes;
  • Sejam sintaticamente, token-a-token, equivalentes;
  • Possuam a mesma semântica.

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

22 of 41

Os "segredos" da ODR

Enquanto que a essência da ODR é clara, os detalhes de sua especificação não são triviais. Em particular, o significado de unicidade relativo a uma entidade é o seguinte.

Classes

O compilador precisa saber o tamanho (em bytes) da classe. Só assim é possível calcular o espaço ocupado por suas instâncias em memória.

Templates

A definição da classe ou função deve estar disponível para que o compilador possa instanciar o tipo concreto correspondente à entidade parametrizada.

22

  • São consideradas únicas classes, templates ou funções inline que:
  • Apareçam em translation units diferentes;
  • Sejam sintaticamente, token-a-token, equivalentes;
  • Possuam a mesma semântica.

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

23 of 41

Os "segredos" da ODR

Enquanto que a essência da ODR é clara, os detalhes de sua especificação não são triviais. Em particular, o significado de unicidade relativo a uma entidade é o seguinte.

Classes

O compilador precisa saber o tamanho (em bytes) da classe. Só assim é possível calcular o espaço ocupado por suas instâncias em memória.

Templates

A definição da classe ou função deve estar disponível para que o compilador possa instanciar o tipo concreto correspondente à entidade parametrizada.

Funções inline

Para que a corpo de uma função seja embutido no código objeto, ao invés de apenas uma chamada, sua implementação deve estar acessível.

23

  • São consideradas únicas classes, templates ou funções inline que:
  • Apareçam em translation units diferentes;
  • Sejam sintaticamente, token-a-token, equivalentes;
  • Possuam a mesma semântica.

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

24 of 41

Não conte com o compilador...

A verificação de consistência da ODR não é trivial, e, tipicamente, não é realizada por completo. Se violada, pode levar a erros em tempo de execução que são de difícil diagnóstico.

24

client.cpp

server.cpp

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

25 of 41

Ligação interna e externa

  • Ligação interna

O nome só pode ser referenciado em sua própria unidade.

  • Ligação externa

O nome pode ser referenciado por todo programa (de outras unidades).

O mecanismo de #include é essencialmente uma conveniência para evitar a reescrita manual de declarações e definições nos arquivos fontes. Ao final, o linker "unifica" os nomes de todas as translation units do programa.

25

Qual a diferença entre uma declaração e uma definição?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

26 of 41

Ligação interna e externa

  • Ligação interna

O nome só pode ser referenciado em sua própria unidade.

  • Ligação externa

O nome pode ser referenciado por todo programa (de outras unidades).

O mecanismo de #include é essencialmente uma conveniência para evitar a reescrita manual de declarações e definições nos arquivos fontes. Ao final, o linker "unifica" os nomes de todas as translation units do programa.

26

Qual a diferença entre uma declaração e uma definição?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

27 of 41

Ligação interna e externa

O mecanismo de #include é essencialmente uma conveniência para evitar a reescrita manual de declarações e definições nos arquivos fontes. Ao final, o linker "unifica" os nomes de todas as translation units do programa.

27

Qual a diferença entre uma declaração e uma definição?

Qual o linkage de static?

unit1.ii

unit2.ii

Qual o linkage padrão de const?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

28 of 41

Headers pré-compilados ao resgate

Com intuito de amenizar o problema de desempenho da inclusão textual, vários compiladores oferecem headers pré-compilados. Na teoria, a idéia é legal, mas, na prática, não escala tão bem.

28

entireworld.h

entireworld.gch

q.cpp

p.cpp

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

29 of 41

Headers pré-compilados ao resgate

Com intuito de amenizar o problema de desempenho da inclusão textual, vários compiladores oferecem headers pré-compilados. Na teoria, a idéia é legal, mas, na prática, não escala tão bem.

O problema é a invalidação do header pré-compilado:

  • Quanto maior o header pré-compilado, melhor. Porém, maiores também são as chances de um header por ele incluído ser modificado.

29

entireworld.h

entireworld.gch

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

30 of 41

Headers pré-compilados ao resgate

Com intuito de amenizar o problema de desempenho da inclusão textual, vários compiladores oferecem headers pré-compilados. Na teoria, a idéia é legal, mas, na prática, não escala tão bem.

O problema é a invalidação do header pré-compilado:

  • Quanto maior o header pré-compilado, melhor. Porém, maiores também são as chances de um header por ele incluído ser modificado.
  • Não pode haver nenhum token de C++ antes do #include do header pré-compilado - tipicamente, ele é o primeiro item em um fonte.

30

entireworld.h

entireworld.gch

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

31 of 41

Headers pré-compilados ao resgate

Com intuito de amenizar o problema de desempenho da inclusão textual, vários compiladores oferecem headers pré-compilados. Na teoria, a idéia é legal, mas, na prática, não escala tão bem.

O problema é a invalidação do header pré-compilado:

  • Quanto maior o header pré-compilado, melhor. Porém, maiores também são as chances de um header por ele incluído ser modificado.
  • Não pode haver nenhum token de C++ antes do #include do header pré-compilado - tipicamente, ele é o primeiro item em um fonte.
  • As macros passadas para o compilador devem ser exatamente as mesmas daquelas usadas na construção do header pré-compilado.

31

entireworld.h

entireworld.gch

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

32 of 41

Headers pré-compilados ao resgate

Com intuito de amenizar o problema de desempenho da inclusão textual, vários compiladores oferecem headers pré-compilados. Na teoria, a idéia é legal, mas, na prática, não escala tão bem.

O problema é a invalidação do header pré-compilado:

  • Quanto maior o header pré-compilado, melhor. Porém, maiores também são as chances de um header por ele incluído ser modificado.
  • Não pode haver nenhum token de C++ antes do #include do header pré-compilado - tipicamente, ele é o primeiro item em um fonte.
  • As macros passadas para o compilador devem ser exatamente as mesmas daquelas usadas na construção do header pré-compilado.
  • Certas opções de compilação precisam ser especificadas para todos os fontes que dependem do header pré-compilado.
  • Apenas um header pré-compilado pode existir no programa.

32

entireworld.h

entireworld.gch

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

33 of 41

Headers pré-compilados ao resgate

Com intuito de amenizar o problema de desempenho da inclusão textual, vários compiladores oferecem headers pré-compilados. Na teoria, a idéia é legal, mas, na prática, não escala tão bem.

O problema é a invalidação do header pré-compilado:

  • Quanto maior o header pré-compilado, melhor. Porém, maiores também são as chances de um header por ele incluído ser modificado.
  • Não pode haver nenhum token de C++ antes do #include do header pré-compilado - tipicamente, ele é o primeiro item em um fonte.
  • As macros passadas para o compilador devem ser exatamente as mesmas daquelas usadas na construção do header pré-compilado.
  • Certas opções de compilação precisam ser especificadas para todos os fontes que dependem do header pré-compilado.
  • Apenas um header pré-compilado pode existir no programa.

33

entireworld.h

entireworld.gch

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

34 of 41

Impacto em ferramentas de análise

  • Como fazer parsing de código que depende de flag passada ao pré-processador via -D?

A complexidade resultante do modelo de inclusão textual não se reflete apenas no processo de compilação. Ela é também um fator impactante para a construção de ferramentas de análise estática.

Note, inclusive, que a não-expansão de check torna o código sintaticamente inválido!

34

Por que refatoração automática de código em linguagens como Java funciona melhor do que em C++?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

35 of 41

Impacto em ferramentas de análise

  • Como fazer parsing de código que depende de flag passada ao pré-processador via -D?
  • Como distinguir entre uma função e uma function-like macro para oferecer refatoração?

A complexidade resultante do modelo de inclusão textual não se reflete apenas no processo de compilação. Ela é também um fator impactante para a construção de ferramentas de análise estática.

Note que renomear uma macro não é tão simples quanto renomear uma função.

35

Por que refatoração automática de código em linguagens como Java funciona melhor do que em C++?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

36 of 41

Impacto em ferramentas de análise

  • Como fazer parsing de código que depende de flag passada ao pré-processador via -D?
  • Como distinguir entre uma função e uma function-like macro para oferecer refatoração?
  • Pior ainda, como renomear uma função que não existe no texto do programa?

A complexidade resultante do modelo de inclusão textual não se reflete apenas no processo de compilação. Ela é também um fator impactante para a construção de ferramentas de análise estática.

36

Por que refatoração automática de código em linguagens como Java funciona melhor do que em C++?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

37 of 41

Impacto em ferramentas de análise

  • Como fazer parsing de código que depende de flag passada ao pré-processador via -D?
  • Como distinguir entre uma função e uma function-like macro para oferecer refatoração?
  • Pior ainda, como renomear uma função que não existe no texto do programa?
  • Qual auto-completion gerar se a definição do objeto depende de um diretiva de compilação?

A complexidade resultante do modelo de inclusão textual não se reflete apenas no processo de compilação. Ela é também um fator impactante para a construção de ferramentas de análise estática.

37

Por que refatoração automática de código em linguagens como Java funciona melhor do que em C++?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

38 of 41

Moral da história

38

Pense duas vezes antes de fazer um #include ou de criar macros.

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

39 of 41

Moral da história

39

Na dúvida, pense mais uma vez.

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

40 of 41

Algumas referências

40

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com

41 of 41

41

Curso de C++ moderno

Leandro T. C. Melo, PhD

www.ltcmelo.com

LTCMELO@GMAIL.COM

Perguntas?

© Leandro T. C. Melo - www.ltcmelo.com

© Leandro T. C. Melo - www.ltcmelo.com