Compatibilidade de C e C ++ - Compatibility of C and C++

As linguagens de programação C e C ++ estão intimamente relacionadas, mas têm muitas diferenças significativas. C ++ começou como uma bifurcação de um C pré- padronizado e foi projetado para ser compatível com o código-fonte e link com os compiladores C da época. Devido a isso, as ferramentas de desenvolvimento para as duas linguagens (como IDEs e compiladores ) são frequentemente integradas em um único produto, com o programador capaz de especificar C ou C ++ como sua linguagem de origem.

No entanto, C não é um subconjunto de C ++ e os programas C não triviais não serão compilados como código C ++ sem modificação. Da mesma forma, C ++ apresenta muitos recursos que não estão disponíveis em C e, na prática, quase todo o código escrito em C ++ não está em conformidade com o código C. Este artigo, no entanto, enfoca as diferenças que fazem com que o código C em conformidade seja um código C ++ malformado ou em conformidade / bem formado em ambas as linguagens, mas se comporte de maneira diferente em C e C ++.

Bjarne Stroustrup , o criador do C ++, sugeriu que as incompatibilidades entre C e C ++ deveriam ser reduzidas tanto quanto possível, a fim de maximizar a interoperabilidade entre as duas linguagens. Outros argumentaram que, como C e C ++ são duas linguagens diferentes, a compatibilidade entre elas é útil, mas não vital; de acordo com esse campo, os esforços para reduzir a incompatibilidade não devem impedir as tentativas de melhorar cada idioma isoladamente. A justificativa oficial para o padrão C de 1999 ( C99 ) "endossa [d] o princípio de manter o maior subconjunto comum" entre C e C ++ "enquanto mantém uma distinção entre eles e permite que eles evoluam separadamente", e afirmou que os autores eram "conteúdo em deixar C ++ ser a linguagem grande e ambiciosa."

Várias adições de C99 não são suportadas no padrão C ++ atual ou entram em conflito com recursos C ++, como matrizes de comprimento variável , tipos de números complexos nativos e o restrict qualificador de tipo . Por outro lado, o C99 reduziu algumas outras incompatibilidades em comparação com o C89, incorporando recursos do C ++, como //comentários e declarações e códigos mistos.

Construções válidas em C, mas não em C ++

C ++ impõe regras de tipagem mais rígidas (sem violações implícitas do sistema de tipo estático) e requisitos de inicialização (imposição de tempo de compilação para que as variáveis ​​no escopo não tenham a inicialização subvertida) do que C e, portanto, algum código C válido é inválido em C ++. Uma justificativa para isso é fornecida no Anexo C.1 do padrão ISO C ++.

  • Uma diferença comumente encontrada é C sendo mais fraco em relação aos ponteiros. Especificamente, C permite que um void*ponteiro seja atribuído a qualquer tipo de ponteiro sem uma conversão, enquanto C ++ não; esse idioma aparece com frequência no código C usando mallocalocação de memória ou na passagem de ponteiros de contexto para a API POSIX pthreads e outras estruturas que envolvem retornos de chamada . Por exemplo, o seguinte é válido em C, mas não em C ++:
    void *ptr;
    /* Implicit conversion from void* to int* */
    int *i = ptr;
    

    ou similarmente:

    int *j = malloc(5 * sizeof *j);     /* Implicit conversion from void* to int* */
    

    Para fazer a compilação do código como C e C ++, deve-se usar um elenco explícito, como segue (com algumas ressalvas em ambas as linguagens):

    void *ptr;
    int *i = (int *)ptr;
    int *j = (int *)malloc(5 * sizeof *j);
    
  • C ++ tem regras mais complicadas sobre atribuições de ponteiro que adicionam qualificadores, uma vez que permite a atribuição de int ** para, const int *const *mas não a atribuição insegura a, const int **enquanto C não permite nenhuma delas (embora os compiladores geralmente emitem apenas um aviso).
  • C ++ muda algumas funções da biblioteca padrão C para adicionar funções sobrecarregadas adicionais com const qualificadores de tipo , por exemplo, strchrretornos char*em C, enquanto C ++ age como se houvesse duas funções sobrecarregadas const char *strchr(const char *)e a char *strchr(char *).
  • C ++ também é mais estrito em conversões para enums: os ints não podem ser convertidos implicitamente em enums como em C. Além disso, constantes deenum enumeração ( enumeradores) são sempre do tipo intem C, enquanto são tipos distintos em C ++ e podem ter um tamanho diferente daquele de int.
  • Em C ++, uma constvariável deve ser inicializada; em C isso não é necessário.
  • Os compiladores C ++ proíbem goto ou switch de cruzar uma inicialização, como no seguinte código C99:
    void fn(void)
    {
        goto flack;
        int i = 1;
    flack:
        ;
    }
    
  • Embora sintaticamente válido, a longjmp()resulta em um comportamento indefinido em C ++ se os quadros de pilha saltados incluírem objetos com destruidores não triviais. A implementação do C ++ é livre para definir o comportamento de forma que os destruidores sejam chamados. No entanto, isso impediria alguns usos de longjmp () que de outra forma seriam válidos, como implementação de threads ou corrotinas por longjmping entre pilhas de chamadas separadas - ao saltar da pilha de chamadas inferior para a superior no espaço de endereço global, destruidores seriam chamados para cada objeto na pilha de chamadas inferior. Esse problema não existe em C.
  • C permite várias definições provisórias de uma única variável global em uma única unidade de tradução , o que é inválido como uma violação de ODR em C ++.
    int N;
    int N = 10;
    
  • Em C, declarando um novo tipo com o mesmo nome de um já existente struct, unionou enumé válido, mas é inválida no C ++, porque em C, struct, union, e enumtipos devem ser indicadas como tal, quando o tipo está referenciado ao passo que no C ++, todas as declarações de tais tipos carregam o typedef implicitamente.
    enum BOOL {FALSE, TRUE};
    typedef int BOOL;
    
  • As declarações de função não protótipo (estilo "K&R") são inválidas em C ++; eles ainda são válidos em C, embora tenham sido considerados obsoletos desde a padronização original de C em 1990. (O termo "obsoleto" é um termo definido no padrão ISO C, significando um recurso que "pode ​​ser considerado para retirada em revisões futuras" do padrão.) Da mesma forma, as declarações de função implícita (usando funções que não foram declaradas) não são permitidas em C ++ e são inválidas em C desde 1999.
  • Em C, um protótipo de função sem parâmetros, por exemplo int foo();, implica que os parâmetros não são especificados. Portanto, é legal chamar tal função com um ou mais argumentos , por exemplo foo(42, "hello world"). Em contraste, em C ++ um protótipo de função sem argumentos significa que a função não aceita argumentos e chamar tal função com argumentos é malformado. Em C, a maneira correta de declarar uma função que não recebe argumentos é usando 'void', como em int foo(void);, que também é válido em C ++. Protótipos de função vazia são um recurso obsoleto no C99 (como eram no C89).
  • Em C e C ++, pode-se definir structtipos aninhados , mas o escopo é interpretado de forma diferente: em C ++, um aninhado structé definido apenas dentro do escopo / namespace do externo struct, enquanto em C a estrutura interna também é definida fora da estrutura externa.
  • C permite struct, unione enumtipos para ser declarado em protótipos de função, enquanto que C ++ não.

C99 e C11 adicionaram vários recursos adicionais ao C que não foram incorporados ao C ++ padrão, como números complexos, matrizes de comprimento variável (observe que números complexos e matrizes de comprimento variável são designados como extensões opcionais em C11), membros de matriz flexível , o restrito palavra-chave, qualificadores de parâmetro de matriz, literais compostos e inicializadores designados .

  • A aritmética complexa usando os tipos de dados primitivos float complexe double complexfoi adicionada ao padrão C99 , por meio da _Complexpalavra - chave e complexmacro de conveniência. Em C ++, a aritmética complexa pode ser executada usando a classe de número complexo, mas os dois métodos não são compatíveis com o código. (Os padrões desde C ++ 11 requerem compatibilidade binária, no entanto.)
  • Matrizes de comprimento variável. Esse recurso leva ao operador sizeof de tempo possivelmente não compilado .
    void foo(size_t x, int a[*]);  // VLA declaration
    void foo(size_t x, int a[x]) 
    {
        printf("%zu\n", sizeof a); // same as sizeof(int*)
        char s[x * 2];
        printf("%zu\n", sizeof s); // will print x*2
    }
    
  • O último membro de um tipo de estrutura C99 com mais de um membro pode ser um membro de matriz flexível , que assume a forma sintática de uma matriz com comprimento não especificado. Isso serve a uma finalidade semelhante às matrizes de comprimento variável, mas os VLAs não podem aparecer nas definições de tipo e, ao contrário dos VLAs, os membros da matriz flexível não têm tamanho definido. ISO C ++ não tem esse recurso. Exemplo:
    struct X
    {
        int n, m;
        char bytes[];
    }
    
  • O restrict qualificador de tipo definido no C99 não foi incluído no padrão C ++ 03, mas a maioria dos compiladores convencionais, como GNU Compiler Collection , Microsoft Visual C ++ e Intel C ++ Compiler, fornecem funcionalidade semelhante como uma extensão.
  • Os qualificadores de parâmetro de matriz em funções são suportados em C, mas não em C ++.
    int foo(int a[const]);     // equivalent to int *const a 
    int bar(char s[static 5]); // annotates that s is at least 5 chars long
    
  • A funcionalidade de literais compostos em C é generalizada para tipos internos e definidos pelo usuário pela sintaxe de inicialização de lista do C ++ 11, embora com algumas diferenças sintáticas e semânticas.
    struct X a = (struct X){4, 6};  // The equivalent in C++ would be X{4, 6}. The C syntactic form used in C99 is supported as an extension in the GCC and Clang C++ compilers. 
    foo(&(struct X){4, 6});         // The object is allocated in the stack and its address can be passed to a function. This is not supported in C++.
    
  • Inicializadores designados para matrizes são válidos apenas em C:
    char s[20] = { [0] = 'a', [8] = 'g' };  // allowed in C, not in C++
    
  • Funções que não retornam podem ser anotadas usando um atributo noreturn em C ++ enquanto C usa uma palavra-chave distinta.

C ++ adiciona várias palavras-chave adicionais para oferecer suporte a seus novos recursos. Isso torna o código C usando essas palavras-chave para identificadores inválidos em C ++. Por exemplo:

struct template 
{
    int new;
    struct template* class;
};
é um código C válido, mas é rejeitado por um compilador C ++, uma vez que as palavras-chave "template", "new" e "class" são reservadas.

Construções que se comportam de maneira diferente em C e C ++

Existem algumas construções sintáticas que são válidas em C e C ++, mas produzem resultados diferentes nas duas linguagens.

  • Literais de caracteres como 'a'são do tipo intem C e do tipo charem C ++, o que significa que sizeof 'a'geralmente darão resultados diferentes nas duas linguagens: em C ++, será 1, enquanto em C será sizeof(int). Como outra consequência dessa diferença de tipo, em C, 'a'sempre será uma expressão com charsinal , independentemente de ser ou não um tipo com ou sem sinal, enquanto para C ++ isso é específico da implementação do compilador.
  • C ++ atribui vínculo interno a constvariáveis ​​com escopo de namespace, a menos que sejam explicitamente declaradas extern, ao contrário de C em que externé o padrão para todas as entidades com escopo de arquivo. Observe que, na prática, isso não leva a mudanças semânticas silenciosas entre códigos C e C ++ idênticos, mas, em vez disso, leva a um erro de tempo de compilação ou de ligação.
  • Em C, o uso de funções embutidas requer a adição manual de uma declaração de protótipo da função usando a palavra-chave extern em exatamente uma unidade de tradução para garantir que uma versão não embutida seja vinculada, enquanto C ++ lida com isso automaticamente. Em mais detalhes, C distingue dois tipos de definições de inlinefunções : definições externas comuns (onde extern é explicitamente usado) e definições inline. C ++, por outro lado, fornece apenas definições embutidas para funções embutidas. Em C, uma definição inline é semelhante a uma interna (ou seja, estática), no sentido de que pode coexistir no mesmo programa com uma definição externa e qualquer número de definições internas e inline da mesma função em outras unidades de tradução, todas as quais pode ser diferente. Esta é uma consideração separada da ligação da função, mas não independente. Compiladores C têm a liberdade de escolher entre usar definições internas e externas da mesma função quando ambas estão visíveis. C ++, no entanto, requer que se uma função com ligação externa for declarada inline em qualquer unidade de tradução, então ela deve ser declarada (e, portanto, também definida) em cada unidade de tradução onde for usada, e que todas as definições dessa função sejam idênticas , seguindo o ODR. Observe que as funções embutidas estáticas se comportam de forma idêntica em C e C ++.
  • Ambos C99 e C ++ têm um tipo booleano bool com constantes truee false, mas são definidos de forma diferente. Em C ++, boolé um tipo integrado e uma palavra-chave reservada . Em C99, uma nova palavra-chave,, _Boolé introduzida como o novo tipo booleano. O cabeçalho stdbool.hfornece macros bool, truee falseque são definidos como _Bool, 1e 0, respectivamente. Portanto, truee falsedigite intC.

Várias das outras diferenças da seção anterior também podem ser exploradas para criar código que compila em ambas as linguagens, mas se comporta de maneira diferente. Por exemplo, a função a seguir retornará valores diferentes em C e C ++:

extern int T;

int size(void)
{
    struct T {  int i;  int j;  };
    
    return sizeof(T);
    /* C:   return sizeof(int)
     * C++: return sizeof(struct T)
     */
}

Isso se deve ao fato de C exigir structna frente das tags de estrutura (e assim sizeof(T)se referir à variável), mas C ++ permitir que ela seja omitida (e assim sizeof(T)se referir ao implícito typedef). Esteja ciente de que o resultado é diferente quando a externdeclaração é colocada dentro da função: então a presença de um identificador com o mesmo nome no escopo da função inibe o typedefefeito implícito para C ++, e o resultado para C e C ++ seria o mesmo. Observe também que a ambigüidade no exemplo acima se deve ao uso do parêntese com o sizeofoperador. Usar sizeof Tdeveria Tser uma expressão e não um tipo e, portanto, o exemplo não seria compilado com C ++.

Vinculando código C e C ++

Embora C e C ++ mantenham um alto grau de compatibilidade de origem, os arquivos-objeto que seus respectivos compiladores produzem podem ter diferenças importantes que se manifestam ao misturar códigos C e C ++. Notavelmente:

  • Os compiladores C não nomeiam símbolos mutilados da maneira que os compiladores C ++ fazem.
  • Dependendo do compilador e da arquitetura, também pode acontecer que as convenções de chamada sejam diferentes entre as duas linguagens.

Por esses motivos, para que o código C ++ chame uma função C foo(), o código C ++ deve prototipar foo() com extern "C". Da mesma forma, para o código C chamar uma função C ++ bar(), o código C ++ para bar()deve ser declarado com extern "C".

Uma prática comum para arquivos de cabeçalho para manter a compatibilidade C e C ++ é fazer sua declaração ser extern "C"para o escopo do cabeçalho:

/* Header file foo.h */
#ifdef __cplusplus /* If this is a C++ compiler, use C linkage */
extern "C" {
#endif

/* These functions get C linkage */
void foo();
 
struct bar { /* ... */ };

#ifdef __cplusplus /* If this is a C++ compiler, end C linkage */
}
#endif

As diferenças entre a vinculação C e C ++ e as convenções de chamada também podem ter implicações sutis para o código que usa ponteiros de função . Alguns compiladores produzirão código não funcional se um ponteiro de função declarado extern "C"apontar para uma função C ++ que não está declarada extern "C".

Por exemplo, o seguinte código:

void my_function();
extern "C" void foo(void (*fn_ptr)(void));

void bar()
{
   foo(my_function);
}

Usando o compilador C ++ da Sun Microsystems , isso produz o seguinte aviso:

 $ CC -c test.cc
 "test.cc", line 6: Warning (Anachronism): Formal argument fn_ptr of type
 extern "C" void(*)() in call to foo(extern "C" void(*)()) is being passed
 void(*)().

Isto é porque my_function()não está declarado com C de ligação e convenções de chamada, mas está sendo passado para a função C foo().

Referências

links externos