Como declarar uma estrutura em um cabeçalho que deve ser usado por vários arquivos em c?

115

Se eu tiver um arquivo source.c com uma estrutura:

struct a { 
    int i;
    struct b {
        int j;
    }
};

Como essa estrutura pode ser usada em outro arquivo (ou seja func.c)?

Devo criar um novo arquivo de cabeçalho, declarar a estrutura lá e incluir esse cabeçalho func.c?

Ou devo definir a estrutura inteira em um arquivo de cabeçalho e incluí-la em source.ce func.c? Como a estrutura pode ser declarada externem ambos os arquivos?

Eu deveria typedef? Se sim, como?

Engenheiro de software
fonte
Observe que a definição da estrutura não é válida C. No mínimo deve haver um ponto e vírgula após a chave de fechamento para struct b, mas então sua estrutura adeclara um tipo que não é usado (você provavelmente deve definir um nome de membro, talvez k, após a chave de fechamento interna e antes do ponto e vírgula.
Jonathan Leffler

Respostas:

140

se esta estrutura deve ser usada por algum outro arquivo func.c como fazer isso?

Quando um tipo é usado em um arquivo (ou seja, arquivo func.c), ele deve estar visível. A pior maneira de fazer isso é copiar e colar em cada arquivo de origem necessário.

A maneira certa é colocá-lo em um arquivo de cabeçalho e incluí-lo sempre que necessário.

devemos abrir um novo arquivo de cabeçalho e declarar a estrutura lá e incluir esse cabeçalho no func.c?

Esta é a solução que eu mais gosto, porque torna o código altamente modular. Eu codificaria sua estrutura como:

#ifndef SOME_HEADER_GUARD_WITH_UNIQUE_NAME
#define SOME_HEADER_GUARD_WITH_UNIQUE_NAME

struct a
{ 
    int i;
    struct b
    {
        int j;
    }
};

#endif

Eu colocaria funções que usam essa estrutura no mesmo cabeçalho (a função que é "semanticamente" parte de sua "interface").

E normalmente, eu poderia nomear o arquivo com o nome da estrutura e usar esse nome novamente para escolher as definições dos protetores de cabeçalho.

Se você precisar declarar uma função usando um ponteiro para a estrutura, não precisará da definição completa da estrutura. Uma declaração de encaminhamento simples como:

struct a ;

Será suficiente, e diminui o acoplamento.

ou podemos definir a estrutura total no arquivo de cabeçalho e incluí-la em source.c e func.c?

Esta é outra maneira, um pouco mais fácil, mas menos modular: alguns códigos que precisam apenas de sua estrutura para funcionar ainda precisam incluir todos os tipos.

Em C ++, isso pode levar a complicações interessantes, mas isso está fora do tópico (sem tag C ++), então não vou entrar em detalhes.

a seguir, como declarar essa estrutura como externa em ambos os arquivos. ?

Não consigo entender o ponto, talvez, mas Greg Hewgill tem uma resposta muito boa em seu post Como declarar uma estrutura em um cabeçalho que deve ser usado por vários arquivos em c? .

devemos digitá-lo então como?

  • Se você estiver usando C ++, não.
  • Se você estiver usando C, você deve.

O motivo é que o gerenciamento de C struct pode ser uma dor: você deve declarar a palavra-chave struct em todos os lugares em que ela é usada:

struct MyStruct ; /* Forward declaration */

struct MyStruct
{
   /* etc. */
} ;

void doSomething(struct MyStruct * p) /* parameter */
{
   struct MyStruct a ; /* variable */
   /* etc */
}

Enquanto um typedef permitirá que você escreva sem a palavra-chave struct.

struct MyStructTag ; /* Forward declaration */

typedef struct MyStructTag
{
   /* etc. */
} MyStruct ;

void doSomething(MyStruct * p) /* parameter */
{
   MyStruct a ; /* variable */
   /* etc */
}

É importante que você ainda mantenha um nome para a estrutura. Escrevendo:

typedef struct
{
   /* etc. */
} MyStruct ;

irá apenas criar uma estrutura anônima com um nome digitado, e você não poderá encaminhá-la. Portanto, mantenha o seguinte formato:

typedef struct MyStructTag
{
   /* etc. */
} MyStruct ;

Assim, você será capaz de usar MyStruct em todos os lugares que deseja evitar adicionar a palavra-chave struct e ainda usar MyStructTag quando um typedef não funcionar (ou seja, declaração de encaminhamento)

Editar:

Corrigida a suposição errada sobre a declaração de struct C99, conforme devidamente observado por Jonathan Leffler .

Editar 01/06/2018:

Craig Barnes nos lembra em seu comentário que você não precisa manter nomes separados para o nome de struct "tag" e seu nome "typedef", como fiz acima por uma questão de clareza.

Na verdade, o código acima pode muito bem ser escrito como:

typedef struct MyStruct
{
   /* etc. */
} MyStruct ;

IIRC, isso é realmente o que C ++ faz com sua declaração de struct mais simples, nos bastidores, para mantê-la compatível com C:

// C++ explicit declaration by the user
struct MyStruct
{
   /* etc. */
} ;
// C++ standard then implicitly adds the following line
typedef MyStruct MyStruct;

De volta ao C, eu vi os dois usos (nomes separados e mesmos nomes) e nenhum tem desvantagens que eu conheça, então usar o mesmo nome torna a leitura mais simples se você não usar "namespaces" separados em C para structs e outros símbolos .

paercebal
fonte
2
Você pode comentar ou apontar a seção do padrão C99 que garante o seu comentário "Se você estiver usando o C99, não"?
Jonathan Leffler
Você está certo. Testei recentemente um C99 e fiquei surpreso ao ver que minha estrutura sem tipo não foi reconhecida quando usada da maneira C ++. Eu procurei por opções de compilador, e então, em todos os documentos padrão que pude colocar minhas mãos, mas não encontrei nada que pudesse explicar Eu acreditava que era possível ...
paercebal
2
... De qualquer forma, obrigado pelo comentário. Eu corrigi isso agora.
paercebal
Não há necessidade de usar nomes diferentes para a structtag e o typedefnome. C usa um namespace diferente para structtags, então você pode usar MyStructpara ambos.
Craig Barnes
1
@CraigBarnes Você está certo, mas eu queria que isso ficasse claro apenas lendo o código. Se eu tivesse dado o mesmo nome, poderia ter confundido C novatos sobre a necessidade de escrever o nome * duas vezes "na mesma" declaração ". Vou adicionar uma nota mencionando seu comentário. Obrigado!
paercebal
34

Para uma definição de estrutura que deve ser usada em mais de um arquivo de origem, você definitivamente deve colocá-la em um arquivo de cabeçalho. Em seguida, inclua esse arquivo de cabeçalho em qualquer arquivo de origem que precise da estrutura.

A externdeclaração não é usada para definições de estrutura, mas em vez disso, é usada para declarações de variáveis ​​(ou seja, algum valor de dados com um tipo de estrutura que você definiu). Se você quiser usar a mesma variável em mais de um arquivo de origem, declare-a como externem um arquivo de cabeçalho como:

extern struct a myAValue;

Então, em um arquivo de origem, defina a variável real:

struct a myAValue;

Se você esquecer de fazer isso ou acidentalmente defini-lo em dois arquivos de origem, o vinculador o informará sobre isso.

Greg Hewgill
fonte
Você pode obter um erro de vinculador ou não. Em C, o modelo de ligação permite definições 'comuns', portanto, várias definições sem inicializador (e talvez com o mesmo inicializador) podem funcionar. É uma 'extensão comum'. Alguns compiladores também suportam definições provisórias (ausentes), acredito.
Jonathan Leffler
Então, em um arquivo de origem, defina a variável real:; ... ou acidentalmente defini- lo em dois arquivos de origem ... :)
Johannes Schaub - litb
Uma técnica que usei para o problema de declaração / definição é definir condicionalmente GLOBALcomo externou nada no topo do cabeçalho e, em seguida, declarar as variáveis ​​como GLOBAL struct a myAValue;. Da maioria dos arquivos de origem, você organiza a #define GLOBAL externversão a ser usada ( declarando as variáveis) e de exatamente um arquivo de origem faz com que o define vazio seja usado para que as variáveis ​​sejam definidas .
TripeHound
Você pode ter o nome da estrutura igual ao nome do typedef em C, mas não em C ++.
xuhdev
6

ah:

#ifndef A_H
#define A_H

struct a { 
    int i;
    struct b {
        int j;
    }
};

#endif

pronto, agora você só precisa incluir ah nos arquivos onde deseja usar esta estrutura.

fmsf
fonte