Typedefs repetidos – inválidos em C mas válidos em C ++?

Eu gostaria de uma referência padrão porque o código a seguir desencadeia um aviso de conformidade em C (testado com gcc -pedantic ; “redefinição typedef“), mas está bem em C ++ ( g++ -pedantic ):

 typedef struct Foo Foo; typedef struct Foo Foo; int main() { return 0; } 

Por que não posso definir um typedef repetidamente em C?

(Isso tem implicações práticas para a estruturação de header de um projeto C. )

Por que isso compila em C ++?

Porque o padrão C ++ explicitamente diz isso.

Referência:

C ++ 03 Padrão 7.1.3 typedef specifier

§7.1.3.2:

Em um determinado escopo que não é de class, um especificador typedef pode ser usado para redefinir o nome de qualquer tipo declarado nesse escopo para se referir ao tipo ao qual ele já se refere.

[Exemplo:
typedef struct s {/ * … * /} s;
typedef int eu;
typedef int eu;
typedef II;
– por exemplo

Por que isso não compila em C?

typedef nomes não têm binding e C99 padrão não permite identificadores sem especificação de binding para ter mais de uma declaração com o mesmo escopo e no mesmo espaço de nome.

Referência:

Norma C99: §6.2.2 Ligações de identificadores

§6.2.2 / 6 declara:

Os seguintes identificadores não têm binding: um identificador declarado como algo diferente de um object ou uma function; um identificador declarado como um parâmetro de function; um identificador de escopo de bloco para um object declarado sem o especi fi cador externo da class de armazenamento.

Além disso, o §6.7 / 3 declara:

Se um identificador não tiver vinculação, não deverá haver mais de uma declaração do identificador (em um declarador ou especificador de tipo) com o mesmo escopo e no mesmo espaço de nome , exceto para tags conforme especificado em 6.7.2.3.

O padrão C é agora ISO / IEC 9989: 2011

O padrão 2011 C foi publicado na segunda-feira, 2011-12-19 pela ISO (ou, mais precisamente, o aviso de que foi publicado foi adicionado ao site do comitê no dia 19; o padrão pode ter sido publicado como “há muito tempo” como 2011-12-08). Veja o anúncio no site do WG14 . Infelizmente, o PDF da ISO custa 338 CHF e de ANSI 387 USD .

  • Você pode obter o PDF para INCITS / ISO / IEC 9899: 2012 (C2011) do ANSI por 30 USD.
  • Você pode obter o PDF para INCITS / ISO / IEC 14882: 2012 (C ++ 2011) do ANSI por 30 USD.

Resposta Principal

A questão é “São repetidos typedefs permitidos em C”? A resposta é “Não – não nas normas ISO / IEC 9899: 1999 ou 9899: 1990”. A razão é provavelmente histórica; os compiladores C originais não permitiam, portanto, os padronizadores originais (que eram obrigados a padronizar o que já estava disponível nos compiladores C) padronizaram esse comportamento.

Veja a resposta de Als para onde o padrão C99 proscreve typedefs repetidos. O padrão C11 mudou a regra em §6.7 ¶3 para:

3 Se um identificador não tiver vinculação, não haverá mais de uma declaração do identificador (em um declarador ou especificador de tipo) com o mesmo escopo e no mesmo espaço de nome, exceto que:

  • um nome typedef pode ser redefinido para indicar o mesmo tipo que atualmente, desde que o tipo não seja um tipo modificado de forma variável;
  • tags podem ser redeclaradas conforme especificado em 6.7.2.3.

Portanto, há agora um mandato explícito de um typedef repetido em C11. Role sobre a disponibilidade de compiladores C compatíveis com C11.


Para aqueles que ainda usam o C99 ou anterior, a pergunta subsequente é, presumivelmente, “Então, como evito problemas com repetidos typedefs?”

Se você seguir a regra de que há um único header que define cada tipo que é necessário em mais de um arquivo de origem (mas pode haver muitos headers definindo esses tipos; cada tipo separado é encontrado em apenas um header), e se esse header é usado sempre que o tipo é necessário, então você não entra no conflito.

Você também pode usar declarações de estrutura incompletas se precisar apenas de pointers para os tipos e não precisar alocar a estrutura real ou acessar os membros deles (tipos opacos). Novamente, defina regras sobre qual header declara o tipo incompleto e use esse header sempre que o tipo for necessário.

Veja também O que são variables ​​externas em C ; Ele fala sobre variables, mas os tipos podem ser tratados de maneira semelhante.


Pergunta dos comentários

Preciso muito das “declarações incompletas de estrutura”, devido a complicações de pré-processadores separadas que proíbem certas inclusões. Então você está dizendo que eu não devo digitar as declarações para frente se elas forem digitadas novamente pelo header completo?

Mais ou menos. Eu realmente não tive que lidar com isso (embora existam partes dos sistemas em funcionamento que chegam muito perto de ter que se preocupar com isso), então isso é um pouco hesitante, mas acredito que deveria funcionar.

Geralmente, um header descreve os serviços externos fornecidos por uma ‘biblioteca’ (um ou mais arquivos de origem) com detalhes suficientes para os usuários da biblioteca poderem compilar com ela. Especialmente no caso em que há vários arquivos de origem, também pode haver um header interno que defina, por exemplo, os tipos completos.

Todos os headers são (a) independentes e (b) idempotentes. Isso significa que você pode (a) include o header e todos os outros headers necessários são automaticamente incluídos, e (b) você pode include o header várias vezes sem incorrer na ira do compilador. O último é normalmente conseguido com guardas de header, embora alguns prefiram #pragma once – mas isso não é portátil.

Então, você pode ter um header público como este:

public.h

 #ifndef PUBLIC_H_INCLUDED #define PUBLIC_H_INCLUDED #include  // size_t typedef struct mine mine; typedef struct that that; extern size_t polymath(const mine *x, const that *y, int z); #endif /* PUBLIC_H_INCLUDED */ 

Até agora, não é muito controverso (embora se possa legitimamente suspeitar que a interface fornecida por essa biblioteca é muito incompleta).

private.h

 #ifndef PRIVATE_H_INCLUDED #define PRIVATE_H_INCLUDED #include "public.h" // Get forward definitions for mine and that types struct mine { ... }; struct that { ... }; extern mine *m_constructor(int i); ... #endif /* PRIVATE_H_INCLUDED */ 

Mais uma vez, não muito controverso. O header public.h deve ser listado primeiro; isso fornece uma verificação automática de autocontenção.

Código do consumidor

Qualquer código que precise das gravações dos serviços polymath() :

 #include "public.h" 

Essa é toda a informação necessária para usar o serviço.

Código do provedor

Qualquer código na biblioteca que define as gravações dos serviços polymath() :

 #include "private.h" 

Depois disso, tudo funciona normalmente.

Outro código de provedor

Se houver outra biblioteca (chamada de multimath() ) que usa os serviços polymath() , esse código inclui o public.h como qualquer outro consumidor. Se os serviços polymath() public.h parte da interface externa para multimath() , então o header public public.h includeá public.h (desculpe, eu mudei terminologias perto do fim, aqui). Se os serviços multimath() ocultarem completamente os serviços polymath() , o header public.h não includeá public.h , mas o multimath() private header poderá fazer isso ou os arquivos de origem individuais que precisarem do polymath() serviços podem include quando necessário.

Contanto que você siga religiosamente a disciplina de include o header correto em todos os lugares, você não terá problemas de dupla definição.

Se você descobrir posteriormente que um dos seus headers contém dois grupos de definições, um que pode ser usado sem conflito e outro que pode às vezes (ou sempre) entrar em conflito com algum novo header (e os serviços declarados), então você precisa dividir o header. header original em dois sub-headers. Cada subheader segue individualmente as regras elaboradas aqui. O header original se torna trivial – um protetor de header e linhas para include os dois arquivos individuais. Todo o código de trabalho existente permanece inalterado – embora as dependencies mudem (arquivos extras para depender). O novo código agora pode include o sub-header aceitável relevante enquanto também usa o novo header que entra em conflito com o header original.

Claro, você pode ter dois headers simplesmente irreconciliáveis. Para um exemplo planejado, se houver um header (mal projetado) que declare uma versão diferente da estrutura FILE (da versão em ), você será escolhido; código pode include o header mal projetado ou mas não ambos. Nesse caso, o header mal projetado deve ser revisado para usar um novo nome (talvez File , mas talvez outra coisa). Você pode se deparar mais realisticamente com esse problema se tiver que mesclar o código de dois produtos em um após uma aquisição corporativa, com algumas estruturas de dados comuns, como DB_Connection para uma conexão de database. Na ausência do recurso de namespace do C ++, você está preso a um exercício de renomeação para um ou ambos os lotes de código.

Você pode fazer isso em C ++ por causa de 7.1.3 / 3 e / 4.

Você não pode fazê-lo em C99 porque ele não possui nenhum caso especial equivalente em 6.7.7, portanto, declarar novamente um nome typedef segue as mesmas regras que re-declarar qualquer outro identificador. Especificamente 6.2.2 / 6 (typedefs não possuem linkage) e 6.7 / 3 (identificadores sem linkage só podem ser declarados uma vez com o mesmo escopo).

Lembre-se typedef é um especificador de class de armazenamento em C99, enquanto em C ++ é um especificador decl. A gramática diferente me leva a suspeitar que os autores de C ++ decidiram se esforçar mais para tornar os typedefs “um tipo diferente de declaração”, e assim podem estar dispostos a gastar mais tempo e textos em regras especiais para eles. Além disso, não sei o que a motivação dos autores de C99 (falta de) era.

[Edit: veja a resposta de Johannes para C1x. Eu não estou seguindo isso, então eu provavelmente deveria parar de usar “C” para dizer “C99” porque eu provavelmente nem notarei quando eles ratificarem e publicarem. Já é ruim o suficiente: “C” deveria significar “C99”, mas na prática significa “C99 se você tiver sorte, mas se você tiver que suportar MSVC então C89”.]

[Edite novamente: e, de fato, foi publicado e agora é o C11. Woot.]

Não há nada na especificação c que diga por que isso é inválido. A especificação é o lugar errado para esclarecer isso. FWIW é permitido no C1x (de acordo com uma resposta que recebi para uma das minhas últimas perguntas).

Suponho que esse recurso c1x ofereça suporte à transformação de macros em typedefs (os primeiros podem ser repetidos se forem idênticos).

Muitas pessoas responderam, referindo-se aos padrões, mas ninguém disse, PORQUE os padrões diferem para C e C ++ aqui. Bem, eu acredito, a razão para permitir que typedefs repetidos em C ++ fosse, que o C ++ declara implicitamente estruturas e classs como tipos. Então, o seguinte é legal em C ++:

 struct foo { int a; int b; }; foo f; 

Em C, tem que escrever:

 struct foo { int a; int b; }; typedef struct foo foo; foo f; 

Há muito código C como esse, que declara structs como tipos. Se esse código for migrado para C ++, os typedefs se tornarão duplicados, porque a linguagem C ++ adiciona seus próprios typedefs implícitos. Portanto, para evitar que os programadores se desassociem para remover os typedefs que não são mais necessários, eles permitiram datilografar duplicatas em C ++ desde o início.

Como outros disseram, as pessoas com o tempo perceberam que permitir repetidos typedefs idênticos em C também poderia ser útil. Pelo menos, não deveria prejudicar. É por isso que esse recurso C ++ foi “backportado” para o C11.