Quiz – Definição múltipla de variáveis globais

definição

Quem ainda não sofreu com erro de compilação com relação à definição múltipla de uma mesma variável ou função? Pois bem, com o objetivo de ajudar a elucidar as dúvidas mais básicas de programação em C, preparamos um exemplo bem simples de um cenário de definição múltipla de variáveis globais num projeto. Veja um exemplo:

main.c

#include <stdio.h>
#include "bar.h"

int foo;

int main(void)
{
  printf("foo = %d\n", foo);
  bar_print_foo();

  return 0;
}

 bar.c

#include <stdio.h>

int foo = 2;

void bar_print_foo(void)
{
  printf("foo de bar = %d\n", foo);
}

 bar.h

#ifndef _BAR_
#define _BAR_

extern void bar_print_foo(void);

#endif /* _BAR_ */

 Compilação

$ gcc -Wall main.c bar.c -o mult_def

O que acontece na compilação? Ela é bem sucedida? Ou, ainda, ocorre um erro na link-edição do binário executável? Pode-se, também, gerar um arquivo executável com sucesso, no entanto a sua execução poderia apresentar um comportamento indesejado. Participem desse quiz e fiquem à vontade para comentar!

Resposta

Chegou a hora da resposta! Esse quiz contou com 52 votos. As alternativas oferecidas e os respectivos números de votos são:

  • Mensagens: foo = 2 foo de bar = 2 (12 votos – 23%)
  • Mensagens: foo = 0 foo de bar = 0 (3 votos – 6%)
  • Erro de compilação (10 votos – 19%)
  • Erro de link-edição (27 votos – 52%)

As regras do jogo são estabelecidas pelo padrão da linguagem C. É um trabalho de advogado! Consultando o documento ISO/IEC 9899:TC3, conhecido como C99, no item 6.9.2, temos o seguinte texto:

6.9.2 External object definitions

Semantics

1 If the declaration of an identifier for an object has file scope and an initializer, the declaration is an external definition for the identifier.

2 A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier static, constitutes a tentative definition. If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.

No nosso exemplo, a declaração de foo em bar.c é uma definição, pois requisita ao compilador a alocação de espaço na memória do sistema para a variável em questão. Trata-se de um identicador com linkagem externa e escopo de arquivo. A seguir podemos ver como o compilador tratou essa variável:

$ gcc -c -Wall bar.c -o bar.o
$ nm bar.o
0000000000000000 T bar_print_foo
0000000000000000 D foo
                 U printf

O compilador alocou a variável foo na seção de dados inicializados. Perfeito! Essa inicialização é realizada antes da chamada à função main(), na etapa de start-up.

Mas o que acontece com a declaração da variável foo em main.c? Ela é tratada, de acordo com o padrão da linguagem, como uma tentative definition com linkagem externa, pois não oferece um inicializador e não contém o especificador de armazenamento static. Esse tipo de identificador é tratado no GCC como um símbolo comum, como pode ser observado no arquivo binário, no formato ELF, main.o:

$ gcc -c -Wall main.c -o main.o
$ nm main.o
                 U bar_print_foo
0000000000000004 C foo
0000000000000000 T main
                 U printf

Esse tipo de símbolo representa dados não inicializados. Múltiplos símbolos comuns podem existir ao longo de outros arquivos-objeto, sendo que somente uma definição com um inicializador pode existir. Quando essa última é encontrada pelo linker, as outras declarações são tratadas como referências não definidas.

Veja que no binário executável ELF final, o símbolo foo é alocado na seção de dados inicializados.

$ gcc -Wall main.c bar.c -o mult_def
$ nm mult_def 
0000000000400544 T bar_print_foo
000000000060103c B __bss_start
000000000040043c t call_gmon_start
000000000060103c b completed.6744
0000000000601028 D __data_start
0000000000601028 W data_start
0000000000400460 t deregister_tm_clones
00000000004004d0 t __do_global_dtors_aux
0000000000600e18 t __do_global_dtors_aux_fini_array_entry
0000000000601030 D __dso_handle
0000000000600e28 d _DYNAMIC
000000000060103c D _edata
0000000000601040 B _end
0000000000400604 T _fini
0000000000601038 D foo
00000000004004f0 t frame_dummy
0000000000600e10 t __frame_dummy_init_array_entry
0000000000400728 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
00000000004003c8 T _init
0000000000600e18 t __init_array_end
0000000000600e10 t __init_array_start
0000000000400610 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000600e20 d __JCR_END__
0000000000600e20 d __JCR_LIST__
                 w _Jv_RegisterClasses
0000000000400600 T __libc_csu_fini
0000000000400570 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
000000000040051c T main
                 U printf@@GLIBC_2.2.5
0000000000400490 t register_tm_clones
0000000000400410 T _start
0000000000601040 D __TMC_END__

Portanto, não existe erro nem de compilação, nem de link-edição. E a resposta é:

Mensagens: foo = 2 foo de bar = 2

Acharam que era outra resposta? Deixem seus comentários justificando as suas escolhas!

Muito obrigado pessoal pela participação! Teremos outros quizzes pela frente. Quer saber quando teremos mais um? Estejam ligados nas nossas redes sociais:

Facebook: https://www.facebook.com/osembarcados

LinkedIn: https://www.linkedin.com/groups/Os-Embarcados-6511589

Twitter: @embarcados

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
4 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
AdrianoOP
AdrianoOP
09/02/2014 14:07

Se eu tivesse:

no arquivo bar.c:

int foo = 2;

void bar_print_foo(void)
{
foo = 0;
printf(“foo de bar = %dn”, foo);
}

e no main.c:

int foo;

int main(void)
{
foo = 1;
printf(“foo = %dn”, foo);
bar_print_foo();

return 0;
}

qual seria o resultado?

Henrique Persico Rossi
Reply to  AdrianoOP
09/02/2014 14:20

Adriano, O resultado seria: foo = 1 foo de bar = 0 No exemplo mencionado não são alteradas as declarações/definições da variável foo, e sim somente são adicionadas algumas atribuições à ela ao longo do código. Portanto: A variável foo, antes de ser alterada pela função main(), é inicializada com o valor 2. Em main() seu valor é modificado explicitamente para 1, o que faz a chamada à função printf() exibir a saída: foo = 1 Quando a função bar_print_foo() é chamada, o valor da variável foo é alterado novamente, agora para 0. Assim, a chamada à função printf exibi… Leia mais »

AdrianoOP
AdrianoOP
09/02/2014 13:57

Hahaha! Muito bom!

Será que existe algum tipo de diretiva de compilação que possa alterar este resultado ou causar o erro “esperado” (erro de link de acordo com 52% das respostas)?

Henrique Persico Rossi
Reply to  AdrianoOP
09/02/2014 14:07

Olá Adriano!

Muito boa a pergunta! Não existe uma diretiva de compilação para gerar esse comportamento. Mas existe uma forma de causar um erro de link-edição: criando duas definições da variável foo, ambas com linkagem externa e com inicializadores. Exemplo:

Em main.c, altere a declaração da variável foo, por exemplo, para:

int foo = 3;

O valor de inicialização não importa, contanto que ele seja explícito.

Compile! O erro de link-edição ocorre, mencionando que uma declaração múltipla da variável foo foi encontrada.

Abraços,
Henrique

Home » Software » Linguagem C » Quiz – Definição múltipla de variáveis globais

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: