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 usandomalloc
alocaçã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,strchr
retornoschar*
em C, enquanto C ++ age como se houvesse duas funções sobrecarregadasconst char *strchr(const char *)
e achar *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 de
enum
enumeração ( enumeradores) são sempre do tipoint
em C, enquanto são tipos distintos em C ++ e podem ter um tamanho diferente daquele deint
. - Em C ++, uma
const
variá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
,union
ouenum
é válido, mas é inválida no C ++, porque em C,struct
,union
, eenum
tipos 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 exemplofoo(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 emint 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
struct
tipos aninhados , mas o escopo é interpretado de forma diferente: em C ++, um aninhadostruct
é definido apenas dentro do escopo / namespace do externostruct
, enquanto em C a estrutura interna também é definida fora da estrutura externa. - C permite
struct
,union
eenum
tipos 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 complex
edouble complex
foi adicionada ao padrão C99 , por meio da_Complex
palavra - chave ecomplex
macro 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 tipoint
em C e do tipochar
em C ++, o que significa quesizeof '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 comchar
sinal , 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
const
variáveis com escopo de namespace, a menos que sejam explicitamente declaradasextern
, ao contrário de C em queextern
é 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
inline
funçõ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 constantestrue
efalse
, 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çalhostdbool.h
fornece macrosbool
,true
efalse
que são definidos como_Bool
,1
e0
, respectivamente. Portanto,true
efalse
digiteint
C.
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 struct
na 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 extern
declaraçã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 typedef
efeito 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 sizeof
operador. Usar sizeof T
deveria T
ser 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
- Comparação detalhada , frase por frase, de uma perspectiva do padrão C89.
- Incompatibilidades entre ISO C e ISO C ++ , David R. Tribble (agosto de 2001).
- Oracle (Sun Microsystems) C ++ Migration Guide, seção 3.11 , Oracle / Sun compilador docs sobre linkage scope.
- Oracle: combinando códigos C e C ++ no mesmo programa , visão geral de Steve Clamage (presidente do Comitê ANSI C ++).