C e C ++ têm muitas diferenças e nem todo código C válido é válido.
(Por "válido", quero dizer código padrão com comportamento definido, ou seja, não específico da implementação / indefinido / etc.)
Existe algum cenário em que um código válido em C e C ++ produza um comportamento diferente quando compilado com um compilador padrão em cada idioma?
Para fazer uma comparação razoável / útil (estou tentando aprender algo praticamente útil, para não tentar encontrar brechas óbvias na pergunta), vamos assumir:
- Nada relacionado ao pré-processador (o que significa que não há hacks
#ifdef __cplusplus
, pragmas etc.) - Qualquer coisa definida pela implementação é a mesma nos dois idiomas (por exemplo, limites numéricos, etc.)
- Estamos comparando versões razoavelmente recentes de cada padrão (por exemplo, C ++ 98 e C90 ou posterior).
Se as versões importarem, mencione quais versões de cada uma produzem comportamentos diferentes.
Respostas:
O seguinte, válido em C e C ++, resultará (provavelmente) em valores diferentes
i
em C e C ++:Consulte Tamanho do caractere ('a') em C / C ++ para obter uma explicação da diferença.
Outro deste artigo :
fonte
struct
antes dos nomes da estrutura.struct sz { int i[2];};
significaria que C e C ++ precisam produzir valores diferentes. (Considerando que um DSP com sizeof (int) == 1, pode produzir o mesmo valor).Aqui está um exemplo que tira proveito da diferença entre chamadas de função e declarações de objeto em C e C ++, além do fato de que o C90 permite a chamada de funções não declaradas:
No C ++, isso não será impresso porque um temporário
f
é criado e destruído, mas no C90 será impressohello
porque as funções podem ser chamadas sem terem sido declaradas.Caso você esteja se perguntando sobre o nome que
f
está sendo usado duas vezes, os padrões C e C ++ permitem explicitamente isso e, para criar um objeto, é necessáriostruct f
desambiguar se você deseja a estrutura ou deixar de ladostruct
se desejar a função.fonte
Para C ++ vs. C90, há pelo menos uma maneira de obter um comportamento diferente que não está definido pela implementação. O C90 não tem comentários de linha única. Com um pouco de cuidado, podemos usar isso para criar uma expressão com resultados totalmente diferentes no C90 e no C ++.
No C ++, tudo, desde o
//
final da linha, é um comentário, portanto, funciona como:Como o C90 não possui comentários de linha única, apenas o
/* comment */
é um comentário. O primeiro/
e o2
são ambos partes da inicialização, portanto, ele se propõe a:Portanto, um compilador C ++ correto fornecerá 13, mas um compilador C90 estritamente correto 8. É claro, eu apenas escolhi números arbitrários aqui - você pode usar outros números como achar melhor.
fonte
2
, seria lido como10 / + 3
válido (unário +).C90 vs. C ++ 11 (
int
vs.double
):Em C
auto
significa variável local. No C90, não há problema em omitir variável ou tipo de função. O padrão éint
. No C ++ 11auto
significa algo completamente diferente, ele diz ao compilador para inferir o tipo da variável do valor usado para inicializá-la.fonte
auto
?int
por padrão. Isso é inteligente! 1int
.int
. Ainda assim, no mundo real, onde há toneladas de código legado e o líder de mercado ainda não implementou o C99 e não tem intenção de fazê-lo, falar em "uma versão obsoleta do C" é absurdo.Outro exemplo que ainda não vi mencionado, este destaca uma diferença de pré-processador:
Isso imprime "false" em C e "true" em C ++ - em C, qualquer macro indefinida é avaliada como 0. No C ++, há uma exceção: "true" é avaliado como 1.
fonte
#define true false
ಠ_ಠDe acordo com o padrão C ++ 11:
uma. O operador de vírgula executa a conversão de lvalue para rvalue em C, mas não em C ++:
Em C ++, o valor dessa expressão será 100 e em C, será
sizeof(char*)
.b. Em C ++, o tipo de enumerador é seu enum. Em C, o tipo de enumerador é int.
Isso significa que
sizeof(int)
pode não ser igual asizeof(E)
.c. No C ++, uma função declarada com a lista de parâmetros vazia não requer argumentos. Em C, a lista de parâmetros vazios significa que o número e o tipo de parâmetros de função são desconhecidos.
fonte
sizeof(char*)
poderia ser 100; nesse caso, o primeiro exemplo produziria o mesmo comportamento observável em C e C ++ (ou seja, embora o método de obtençãos
fosse diferente,s
acabaria sendo 100). O OP mencionou que esse tipo de comportamento definido pela implementação era bom, pois ele estava apenas querendo evitar respostas de advogados de idiomas, então o primeiro está bem por sua exceção. Mas o segundo é bom em qualquer caso.char arr[sizeof(char*)+1]; int s = sizeof(0, arr);
void *arr[100]
. Nesse caso, um elemento tem o mesmo tamanho de um ponteiro para o mesmo elemento; portanto, enquanto houver 2 ou mais elementos, a matriz deve ser maior que o endereço do seu primeiro elemento.Este programa imprime
1
em C ++ e0
em C:Isso acontece porque há
double abs(double)
sobrecarga no C ++; portanto,abs(0.6)
retorna0.6
enquanto em C ele retorna0
devido à conversão implícita de dupla para int antes de chamarint abs(int)
. Em C, você tem que usarfabs
para trabalhardouble
.fonte
stdlib.h
define apenasabs(int)
eabs(long)
; a versãoabs(double)
é declarada pormath.h
. Portanto, este programa ainda pode chamar aabs(int)
versão. É um detalhe de implementação sestdlib.h
também fazmath.h
com que seja incluído. (Eu acho que seria um bug seabs(double)
fosse chamado, mas outros aspecs demath.h
não foram incluídos).<math.h>
também inclui sobrecargas adicionais; na prática, todos os principais compiladores não incluem essas sobrecargas, a menos que o formulário<cmath>
seja usado.Em C, isso imprime qualquer valor que
sizeof(int)
esteja no sistema atual, que normalmente está4
na maioria dos sistemas atualmente em uso atualmente.No C ++, isso deve imprimir 1.
fonte
%d
não é o especificador de formato certo parasize_t
.Outra
sizeof
armadilha: expressões booleanas.É igual a
sizeof(int)
C, porque a expressão é do tipoint
, mas normalmente é 1 em C ++ (embora não seja necessário). Na prática, eles são quase sempre diferentes.fonte
!
deve ser suficiente para abool
.sizeof(0)
está4
em C e C ++ porque0
é um valor inteiro rvalue.sizeof(!0)
está4
em C e1
em C ++. O lógico NOT opera em operandos do tipo bool. Se o valor int0
for implicitamente convertido emfalse
(um valor booleano), será invertido, resultando emtrue
. Ambostrue
efalse
são valores booleanos em C ++ esizeof(bool)
is1
. No entanto, em C é!0
avaliado como1
, que é um rvalor do tipo int. A linguagem de programação C não possui tipo de dados bool por padrão.A linguagem de programação C ++ (3ª edição) fornece três exemplos:
sizeof ('a'), como @Adam Rosenfield mencionou;
//
comentários sendo usados para criar código oculto:Estruturas etc. ocultando coisas em escopos externos, como no seu exemplo.
fonte
Uma castanha antiga que depende do compilador C, que não reconhece comentários de fim de linha em C ++ ...
fonte
Outro listado pelo padrão C ++:
fonte
x
no topo. Eu pensei que você disse "a matriza
".As funções embutidas no C são padronizadas para o escopo externo, enquanto as do C ++ não.
Compilar os dois arquivos a seguir imprimirá o "Estou em linha" no caso do GNU C, mas nada para o C ++.
Arquivo 1
Arquivo 2
Além disso, o C ++ trata implicitamente qualquer
const
global como astatic
menos que seja declarado explicitamenteextern
, ao contrário de C no qualextern
é o padrão.fonte
extern
que é demonstrado aqui?struct fun
vsfn
) e não tem nada a ver se a função está em linha. O resultado é idêntico se você remover oinline
qualificador.inline
não foi adicionado até C99, mas na C99fun()
não pode ser chamado sem um protótipo no escopo. Então eu assumo esta resposta só se aplica a GNU C.Retorna com o código de saída 0 em C ++ ou 3 em C.
Esse truque provavelmente poderia ser usado para fazer algo mais interessante, mas eu não conseguia pensar em uma boa maneira de criar um construtor que fosse palatável para C. Tentei fazer um exemplo igualmente chato com o construtor de cópia, que deixaria um argumento ser passado, embora de uma maneira não-portátil:
O VC ++ 2005 se recusou a compilar isso no modo C ++, no entanto, reclamando sobre como o "código de saída" foi redefinido. (Eu acho que isso é um bug do compilador, a menos que eu tenha esquecido de repente como programar.) Ele saiu com um código de saída do processo 1 quando compilado como C.
fonte
exit(code)
é uma declaração válida de uma variávelcode
do tipoexit
, aparentemente. (Consulte "análise mais irritante", que é um problema diferente, mas semelhante).Este programa imprime
128
(32 * sizeof(double)
) quando compilado usando um compilador C ++ e4
quando compilado usando um compilador C.Isso ocorre porque C não possui a noção de resolução de escopo. Em C, as estruturas contidas em outras estruturas são colocadas no escopo da estrutura externa.
fonte
32*sizeof(double)
, em vez de 32 embora :))size_t
com%d
Não esqueça a distinção entre os namespaces globais C e C ++. Suponha que você tenha um foo.cpp
e um foo2.c
Agora, suponha que você tenha um main.c e um main.cpp parecidos com este:
Quando compilado como C ++, ele usará o símbolo no espaço para nome global C ++; em C usará o C:
fonte
foo
). Não existem "namespaces globais" separados.Isso é bastante peculiar, pois é válido em C ++ e em C99, C11 e C17 (embora opcional em C11, C17); mas não é válido em C89.
No C99 +, ele cria uma matriz de comprimento variável, que tem suas próprias peculiaridades em relação às matrizes normais, pois possui um tipo de tempo de execução em vez do tipo de tempo de compilação e
sizeof array
não é uma expressão constante inteira em C. No C ++, o tipo é totalmente estático.Se você tentar adicionar um inicializador aqui:
é C ++ válido, mas não C, porque matrizes de tamanho variável não podem ter um inicializador.
fonte
Isso diz respeito a lvalues e rvalues em C e C ++.
Na linguagem de programação C, os operadores de pré-incremento e pós-incremento retornam rvalues, não lvalues. Isso significa que eles não podem estar no lado esquerdo do
=
operador de atribuição. Ambas as instruções fornecerão um erro do compilador em C:No entanto, em C ++, o operador de pré-incremento retorna um lvalue , enquanto o operador de pós-incremento retorna um rvalue. Isso significa que uma expressão com o operador de pré-incremento pode ser colocada no lado esquerdo do
=
operador de atribuição!Agora, por que isso é assim? O pós-incremento incrementa a variável e retorna a variável como era antes do incremento. Este é realmente apenas um rvalue. O valor anterior da variável a é copiado em um registrador como temporário e a é incrementado. Mas o valor anterior de a é retornado pela expressão, é um rvalue. Já não representa o conteúdo atual da variável.
O pré-incremento primeiro incrementa a variável e, em seguida, retorna a variável como era depois que o incremento ocorreu. Nesse caso, não precisamos armazenar o valor antigo da variável em um registro temporário. Apenas recuperamos o novo valor da variável depois que ela foi incrementada. Portanto, o pré-incremento retorna um valor l, retorna a variável a própria. Podemos usar atribuir esse valor a outra coisa, é como a declaração a seguir. Esta é uma conversão implícita de lvalue em rvalue.
Como o pré-incremento retorna um valor l, também podemos atribuir algo a ele. As duas instruções a seguir são idênticas. Na segunda atribuição, primeiro a é incrementado e, em seguida, seu novo valor é substituído por 2.
fonte
Estruturas vazias têm tamanho 0 em C e 1 em C ++:
fonte