Em C, você não pode ter a definição / implementação de função dentro do arquivo de cabeçalho. No entanto, em C ++, você pode ter a implementação completa do método dentro do arquivo de cabeçalho. Por que o comportamento é diferente?
Em C, você não pode ter a definição / implementação de função dentro do arquivo de cabeçalho. No entanto, em C ++, você pode ter a implementação completa do método dentro do arquivo de cabeçalho. Por que o comportamento é diferente?
Respostas:
Em C, se você definir uma função em um arquivo de cabeçalho, essa função aparecerá em cada módulo compilado que inclui esse arquivo de cabeçalho e um símbolo público será exportado para a função. Portanto, se a adição de função for definida no cabeçalho.h, e foo.c e bar.c incluirem o cabeçalho.h, então foo.o e bar.o incluirão cópias da adição.
Quando você vincula esses dois arquivos de objeto, o vinculador verá que o acréscimo de símbolo está definido mais de uma vez e não permitirá.
Se você declarar que a função é estática, nenhum símbolo será exportado. Os arquivos de objeto foo.o e bar.o ainda conterão cópias separadas do código para a função e poderão usá-los, mas o vinculador não poderá ver nenhuma cópia da função, portanto não vai reclamar. Obviamente, nenhum outro módulo poderá ver a função também. E seu programa ficará cheio de duas cópias idênticas da mesma função.
Se você declarar apenas a função no arquivo de cabeçalho, mas não a definir, e depois defini-la em apenas um módulo, o vinculador verá uma cópia da função e todos os módulos do seu programa poderão vê-lo e use-o. E seu programa compilado conterá apenas uma cópia da função.
Assim, você pode ter a definição de função no arquivo de cabeçalho em C, é apenas estilo ruim, forma incorreta e uma péssima idéia geral.
(Por "declarar", quero dizer fornecer um protótipo de função sem um corpo; por "definir" quero dizer fornecer o código real do corpo da função; essa é a terminologia C padrão.)
#ifndef HEADER_H
é o que deveria impedir?
C e C ++ se comportam da mesma forma nesse aspecto - você pode ter inline
funções nos cabeçalhos. No C ++, qualquer método cujo corpo esteja dentro da definição de classe é implicitamente inline
. Se você deseja fazer o mesmo em C, declare as funções static inline
.
static inline
" ... e você ainda terá várias cópias da função em cada unidade de tradução que a usa. No C ++ com não- static
inline
função, você terá apenas uma cópia. Para realmente ter a implementação no cabeçalho em C, você deve 1) marcar a implementação como inline
(por exemplo inline void func(){do_something();}
) e 2) realmente dizer que essa função estará em alguma unidade de tradução específica (por exemplo void func();
).
O conceito de um arquivo de cabeçalho precisa de uma pequena explicação:
Você fornece um arquivo na linha de comando do compilador ou faz um '#include'. A maioria dos compiladores aceita um arquivo de comando com a extensão c, C, cpp, c ++ etc. como arquivo de origem. No entanto, eles geralmente incluem uma opção de linha de comando para permitir o uso de qualquer extensão arbitrária em um arquivo de origem.
Geralmente, o arquivo fornecido na linha de comando é chamado de 'Origem' e o arquivo incluído é chamado de 'Cabeçalho'.
A etapa do pré-processador realmente leva todos eles e faz com que tudo pareça um único arquivo grande para o compilador. O que estava no cabeçalho ou na fonte não é realmente relevante neste momento. Geralmente, há uma opção de um compilador que pode mostrar a saída desse estágio.
Portanto, para cada arquivo fornecido na linha de comando do compilador, um arquivo enorme é fornecido ao compilador. Isso pode ter código / dados que ocuparão memória e / ou criarão um símbolo a ser referenciado a partir de outros arquivos. Agora, cada um deles irá gerar uma imagem de 'objeto'. O vinculador pode fornecer um 'símbolo duplicado' se o mesmo símbolo for encontrado em mais de dois arquivos de objetos que estão sendo vinculados. Talvez seja essa a razão; não é aconselhável colocar código em um arquivo de cabeçalho, o que pode criar símbolos no arquivo de objeto.
Os 'embutidos' geralmente são embutidos ... mas, ao depurar, eles podem não estar embutidos. Então, por que o vinculador não fornece erros multiplamente definidos? Simples ... Esses são símbolos 'fracos' e, desde que todos os dados / códigos de um símbolo fraco de todos os objetos tenham o mesmo tamanho e conteúdo, o vinculado manterá uma cópia e largará a cópia de outros objetos. Funciona.
Citações padrão em C ++
O rascunho padrão do C ++ 17 N4659 10.1.6 "O especificador em linha" diz que os métodos estão implicitamente embutidos:
4 Uma função definida dentro de uma definição de classe é uma função embutida.
e mais adiante, vemos que os métodos em linha não apenas podem, mas devem ser definidos em todas as unidades de tradução:
6 Uma função ou variável em linha deve ser definida em todas as unidades de tradução em que é usada ou deve ter exatamente a mesma definição em todos os casos (6.2).
Isso também é mencionado explicitamente em uma nota em 12.2.1 "Funções-membro":
1 Uma função membro pode ser definida (11.4) em sua definição de classe; nesse caso, é uma função membro em linha (10.1.6) [...]
3 [Nota: Pode haver no máximo uma definição de uma função membro não embutida em um programa. Pode haver mais de uma definição de função de membro em linha em um programa. Veja 6.2 e 10.1.6. - nota final]
Implementação do GCC 8.3
main.cpp
struct MyClass {
void myMethod() {}
};
int main() {
MyClass().myMethod();
}
Compile e visualize símbolos:
g++ -c main.cpp
nm -C main.o
saída:
U _GLOBAL_OFFSET_TABLE_
0000000000000000 W MyClass::myMethod()
U __stack_chk_fail
0000000000000000 T main
então vemos man nm
que o MyClass::myMethod
símbolo está marcado como fraco nos arquivos de objeto ELF, o que implica que ele pode aparecer em vários arquivos de objeto:
"W" "w" O símbolo é um símbolo fraco que não foi identificado especificamente como um símbolo de objeto fraco. Quando um símbolo definido fraco é vinculado a um símbolo definido normal, o símbolo definido normal é usado sem erros. Quando um símbolo indefinido fraco é vinculado e o símbolo não é definido, o valor do símbolo é determinado de maneira específica do sistema, sem erros. Em alguns sistemas, maiúsculas indica que um valor padrão foi especificado.
Provavelmente pelo mesmo motivo que você deve colocar a implementação completa do método dentro da definição de classe em Java.
Eles podem parecer semelhantes, com colchetes irregulares e muitas das mesmas palavras-chave, mas são idiomas diferentes.