O padrão C considera que há um ou dois tipos de 'struct uperms_entry' neste cabeçalho?

Você pode dar capítulo e verso de um dos três padrões C (preferencialmente C99 ou C11) que indica se o seguinte arquivo de cabeçalho tem um ou doisstruct uperms_entry tipos nele?

#ifndef UPERMS_CACHE_INCLUDE
#define UPERMS_CACHE_INCLUDE

typedef struct mutex MT_MUTEX;

typedef struct uperms_cache
{
    MT_MUTEX            *cache_lock;
    int                  processing;
    struct uperms_entry *uperms_list;  // No prior struct uperms_entry
} uperms_cache_t;

typedef struct uperms_entry // Does this define a different struct uperms_entry?
{
    char                 username[32];
    int                  perms;
    struct uperms_entry *next;
} uperms_entry_t;

#endif /* UPERMS_CACHE_INCLUDE */

Perguntas Adjuntas:

Se houver dois tipos, existe alguma maneira de informar o GCC sobre o problema?Se existem dois tipos, isso importa na prática?

(Eu acho que as respostas são 'sim - estritamente existem dois tipos', e então (1) Não e (2) Não)

Contexto: revisão interna do código - Eu gostaria que a ordem das estruturas fosse invertida, mas não tenho certeza se estou sendo totalmente pedante demais.

Atualizar:

Claramente, a resposta para a pergunta inicial é "há umstruct uperms_entrye, portanto, as questões numeradas 1 e 2 são discutíveis. Fico feliz por ter verificado antes de lançar um chiado em uma revisão de código.

Pensamento de fundo

Esta seção foi adicionada muito depois que a questão principal foi resolvida.

Aqui estão algumas citações extensas, mas relevantes, da ISO / IEC 9899: 2011:

§6.2.7 Tipo compatível e tipo composto

¶1 Dois tipos têm tipo compatível se seus tipos forem os mesmos. Regras adicionais para determinar se dois tipos são compatíveis são descritas em 6.7.2 para especificadores de tipo, em 6.7.3 para qualificadores de tipo e em 6.7.6 para declaradores.55) Além disso, dois tipos de estrutura, união ou enumerados declarados em unidades de tradução separadas são compatíveis se suas tags e membros satisfizerem os seguintes requisitos: Se um for declarado com um tag, o outro deverá ser declarado com o mesmo tag. Se ambos estiverem concluídos em qualquer lugar dentro de suas respectivas unidades de tradução, então os seguintes requisitos adicionais se aplicam: deve haver uma correspondência um-para-um entre seus membros, de tal forma que cada par de membros correspondentes seja declarado com tipos compatíveis; se um membro do par for declarado com um especificador de alinhamento, o outro será declarado com um especificador de alinhamento equivalente; e se um membro do par é declarado com um nome, o outro é declarado com o mesmo nome. Para duas estruturas, os membros correspondentes devem ser declarados na mesma ordem. Para duas estruturas ou uniões, os campos de bits correspondentes devem ter as mesmas larguras. Para duas enumerações, os membros correspondentes devem ter os mesmos valores.

55) Dois tipos não precisam ser idênticos para serem compatíveis.

§6.7.2.1 Especificadores de estrutura e união

¶8 A presença de uma struct-declaration-list em um struct-or-union-specifier declara um novo tipo, dentro de uma unidade de tradução. A struct-declaration-list é uma sequência de declarações para os membros da estrutura ou união. Se a struct-declaration-list não contiver nenhum membro nomeado, diretamente ou através de uma estrutura anônima ou união anônima, o comportamento é indefinido. O tipo está incompleto até imediatamente após a} que termina a lista e conclui a partir de então.

§6.7.2.3 Tags

¶4 Todas as declarações de estrutura, união ou tipos enumerados que possuem o mesmo escopo e usam a mesma tag declaram o mesmo tipo. Independentemente de haver uma tag ou outras declarações do tipo na mesma unidade de tradução, o tipo está incompleto129) até imediatamente depois da chave de fechamento da lista que define o conteúdo e conclua a partir de então.

¶5 Duas declarações de estrutura, união ou tipos enumerados que estão em escopos diferentes ou usam tags diferentes declaram tipos distintos. Cada declaração de uma estrutura, união ou tipo enumerado que não inclui uma tag declara um tipo distinto.

¶6 Um especificador de tipo do formulário

struct-or-union identifieroptar { struct-declaration-list }

ou

enum identifieroptar { enumerator-list }

ou

enum identifieroptar { enumerator-list , }

declara uma estrutura, união ou tipo enumerado. A lista define o conteúdo da estrutura, o conteúdo da união ou o conteúdo da enumeração. Se um identificador é fornecido,130) o especificador de tipo também declara o identificador para ser o tag desse tipo.

¶7 Uma declaração do formulário

struct-or-union identifier ;

especifica uma estrutura ou tipo de união e declara o identificador como uma tag desse tipo.131)

¶8 Se um especificador de tipo do formulário

struct-or-union identifier

ocorre como parte de um dos formulários acima, e nenhuma outra declaração do identificador como tag é visível, então declara uma estrutura ou tipo de união incompleta e declara o identificador como a tag desse tipo.131)

¶9 Se um especificador de tipo do formulário

struct-or-union identifier

ou

enum identifier

ocorre como parte de um dos formulários acima, e uma declaração do identificador como um tag é visível, então ele especifica o mesmo tipo que aquela declaração, e não redeclare a tag.

¶12 EXEMPLO 2 Para ilustrar o uso da declaração prévia de um tag para especificar um par de estruturas de referência mútua, as declarações

struct s1 { struct s2 *s2p; /* ... */ }; // D1
struct s2 { struct s1 *s1p; /* ... */ }; // D2

especifique um par de estruturas que contêm ponteiros entre si. Observe, no entanto, que se s2 já foi declarado como um tag em um escopo de inclusão, a declaração D1 se referiria a ele, não à tag s2 declarada em D2. Para eliminar essa sensibilidade de contexto, a declaração

struct s2;

pode ser inserido antes de D1. Isso declara uma nova tag s2 no escopo interno; a declaração D2 completa a especificação do novo tipo.

129) Um tipo incompleto só pode ser usado quando o tamanho de um objeto desse tipo não é necessário. Não é necessário, por exemplo, quando um nome typedef é declarado como um especificador para uma estrutura ou união, ou quando um ponteiro para ou uma função retornando uma estrutura ou união está sendo declarada. (Veja tipos incompletos em 6.2.5.) A especificação deve estar completa antes de tal função ser chamada ou definida.

130) Se não houver identificador, o tipo pode, dentro da unidade de tradução, ser referido apenas pela declaração da qual faz parte. Naturalmente, quando a declaração é de um nome typedef, as declarações subseqüentes podem fazer uso desse nome typedef para declarar objetos com a estrutura especificada, união ou tipo enumerado.

131) Uma construção semelhante com enum não existe.

§6.7.3 Qualificadores de tipo

¶10 Para que dois tipos qualificados sejam compatíveis, ambos devem ter a versão com qualificação idêntica de um tipo compatível; A ordem dos qualificadores de tipo em uma lista de especificadores ou qualificadores não afeta o tipo especificado.

A discussão em §6.7.6 está relacionada a apontadores, arrays e declaradores de função e não afeta realmente estruturas ou uniões.

Eu estava ciente do exemplo 2 quando escrevi a pergunta. Isso é pensar em voz alta sobre algumas das informações acima.

Considere este exemplo, que compila de forma clara:

#include <stdio.h>
struct r1 { int x; };

struct r1;

struct r1 p0;

//struct r1 { int y; };     // Redefinition of struct r1

extern void z(void);

void z(void)
{
    struct r1 p1 = { 23 };
    struct r1;
    //struct r1 p2;         // Storage size of p2 is not known
    struct r2 { struct r1 *rn; int y; };
    struct r1 { struct r2 *rn; int z; };
    struct r2 p = { 0, 1 };
    struct r1 q = { &p, 2 };
    p.rn = &q;
    printf("p.y = %d, q.z = %d\n", p.y, q.z);
    printf("p1.x = %d\n", p1.x);
}

A função ilustra quando o Exemplo 2 se aplica, mas não é um código sensato. A declaração dep1 na função é seria uma estrutura do mesmo tipo que a variável globalp0. Mesmo que o nome do tipo sejastruct r1, é de um tipo diferente (e incompatível) do tipo da variável localp.

A redefinição destruct r1 no nível global não é permitido, independentemente de o elemento ser nomeadox ouy. O priorstruct r1; é um não-op neste contexto.

Uma questão interessante é 'pode funcionarz passarp ouq para qualquer outra função (chamea) A resposta é um 'sim' qualificado e algumas das restrições são interessantes. (Seria também um estilo de codificação apavorante para experimentá-lo, aproximando-se do insano.) A função deve existir em uma unidade de tradução separada (TU). A declaração da função deve estar dentro da funçãoz (porque, se estiver fora da função, seu protótipo deve se referir aostruct r1 definido fora da função, não ostruct r1 definido dentro.

Na outra TU, um certo grau de sanidade deve prevalecer: a funçãoa deve ter os tipos de estrutura compatíveisstruct r1 estruct r2 visível em seu escopo global.

Aqui está outro exemplo, mas este não compila:

#include <stdio.h>

struct r1;
extern void z(struct r1 *r1p);
extern void y(struct r1 *r1p);

void y(struct r1 *r1p)
{
    struct r2 { struct r1 *rn; int y; };
    struct r1 { struct r2 *rn; int z; };
    struct r2 p = { r1p, 1 };
    struct r1 q = { &p, 2 };
    p.rn = &q;
    printf("p.y = %d, q.z = %d\n", p.y, q.z);
}

void z(struct r1 *r1p)
{
    struct r1
    struct r2 { struct r1 *rn; int y; };
    struct r1 { struct r2 *rn; int z; };
    struct r2 p = { r1p, 1 };
    struct r1 q = { &p, 2 };
    p.rn = &q;
    printf("p.y = %d, q.z = %d\n", p.y, q.z);
}

Os avisos do GCC 4.7.1 no Mac OS X 10.7.4 são:

structs3.c: In function 'y':
structs3.c:13:10: warning: assignment from incompatible pointer type [enabled by default]
structs3.c: In function 'z':
structs3.c:22:12: warning: initialization from incompatible pointer type [enabled by default]
structs3.c:22:12: warning: (near initialization for 'p.rn') [enabled by default]

Linhas 13 é a tarefap.rn = &q; em funçãoy e linha 23 é a tentativa de definir e inicializarstruct r2 p em funçãoz.

Isso demonstra que dentro das funções, orn elemento destruct r2 é um ponteiro para o tipo incompletostruct r1 declarada no âmbito global. Adicionando umstruct r1; como a primeira linha de código dentro da função permitiria que o código compilasse, mas a referência de inicializaçãor1p->rn está desreferenciando um ponteiro para um tipo incompleto novamente (o tipo incompleto é ostruct r1 declarada no âmbito global).

As declarações de função e as precedentesstruct r1; linha pode aparecer em um cabeçalho como um tipo opaco. A lista de funções de suporte está incompleta; precisa haver uma maneira de obter um ponteiro para um inicializadostruct r1 para passar para as funções, mas isso é um detalhe.

Para fazer o código funcionar neste segundo TU, os tipos destruct r1 deve ser completo no escopo global antes que as funções sejam definidas, e por causa das referências recursivas, a `struct r21 também deve ser completa.

#include <stdio.h>

/* Logically in a 3-line header file */
struct r1;
extern void z(struct r1 *r1p);
extern void y(struct r1 *r1p);

/* Details private to this TU */
struct r2 { struct r1 *rn; int y; };
struct r1 { struct r2 *rn; int z; };

void y(struct r1 *r1p)
{
    struct r2 p = { r1p,     1 };
    struct r1 q = { r1p->rn, 2 };
    p.rn = &q;
    printf("p.y = %d, q.z = %d\n", p.y, q.z);
}

void z(struct r1 *r1p)
{
    struct r2 p = { r1p,     1 };
    struct r1 q = { r1p->rn, 2 };
    p.rn = &q;
    printf("p.y = %d, q.z = %d\n", p.y, q.z);
}

Esse processo de definir as estruturas no arquivo de implementação enquanto deixa o tipo incompleto no arquivo de cabeçalho público pode ser repetido em vários arquivos de implementação, se necessário, embora se mais de uma TU usar a definição de estrutura completa, seria melhor colocar as definições em um arquivo de cabeçalho privado compartilhado apenas entre os arquivos que implementam as estruturas. Observo que não importa se o cabeçalho privado precede ou segue o cabeçalho público.

Talvez isso já fosse óbvio para você. Eu não precisava pensar nisso antes nesse nível de detalhe.

questionAnswers(2)

yourAnswerToTheQuestion