O código válido em C e C ++ pode produzir um comportamento diferente quando compilado em cada idioma?

664

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.
user541686
fonte
11
A propósito, pode ser útil programar em um dialeto que é C e C ++ ao mesmo tempo. Eu fiz isso no passado e um projeto atual: a linguagem TXR. Curiosamente, os desenvolvedores da linguagem Lua fizeram o mesmo e chamam esse dialeto de "Clean C". Você obtém o benefício de uma melhor verificação do tempo de compilação e, possivelmente, de diagnósticos úteis adicionais dos compiladores C ++, mantendo a portabilidade C.
Kaz
9
Mesclei a pergunta mais antiga nessa questão, pois ela tem mais visualizações e respostas aprovadas. Este ainda é um exemplo de pergunta não construtiva, mas é bastante limítrofe, pois sim, ensina algo aos usuários de SO. Estou fechando como não construtivo apenas para refletir o estado da pergunta antes da mesclagem. Sinta-se livre para discordar e reabrir.
George Stocker
13
Votar para reabrir como eu acho que pode ser objetivamente respondido com um "sim" seguido de um exemplo (como provado abaixo). Eu acho que é construtivo que as pessoas possam aprender um comportamento relevante com isso.
Anders Abel
6
@AndersAbel O número puro de respostas, todas corretas, demonstra inequivocamente que continua a ser uma questão de fazer uma lista. Não havia como você fazer essa pergunta sem obter uma lista.
dmckee --- gatinho ex-moderador
2
@dmckee Para o que vale, eu concordo com você. No entanto, as pessoas com tags C ++ são ... Digamos ... mal-humoradas .
quer

Respostas:

397

O seguinte, válido em C e C ++, resultará (provavelmente) em valores diferentes iem C e C ++:

int i = sizeof('a');

Consulte Tamanho do caractere ('a') em C / C ++ para obter uma explicação da diferença.

Outro deste artigo :

#include <stdio.h>

int  sz = 80;

int main(void)
{
    struct sz { char c; };

    int val = sizeof(sz);      // sizeof(int) in C,
                               // sizeof(struct sz) in C++
    printf("%d\n", val);
    return 0;
}
Alexey Frunze
fonte
8
Definitivamente não estava esperando este! Eu estava esperando algo mais dramático, mas isso ainda é útil, obrigado. :) +1
user541686
17
+1, o segundo exemplo é bom, pois o C ++ não exige structantes dos nomes da estrutura.
Seth Carnegie
1
@ Andrew Eu pensei o mesmo há um tempo atrás e testei e funcionou no GCC 4.7.1 sem o std, ao contrário da minha expectativa. Isso é um bug no GCC?
Seth Carnegie
3
@SethCarnegie: Um programa não conforme não precisa falhar, mas também não é garantido que funcione.
Andrey Vihrov 16/10/12
3
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).
Martin Bonner apoia Monica
464

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:

#include <stdio.h>

struct f { int x; };

int main() {
    f();
}

int f() {
    return printf("hello");
}

No C ++, isso não será impresso porque um temporário fé criado e destruído, mas no C90 será impresso helloporque as funções podem ser chamadas sem terem sido declaradas.

Caso você esteja se perguntando sobre o nome que festá sendo usado duas vezes, os padrões C e C ++ permitem explicitamente isso e, para criar um objeto, é necessário struct fdesambiguar se você deseja a estrutura ou deixar de lado structse desejar a função.

Seth Carnegie
fonte
7
Estritamente falando sob C isso não irá compilar, porque a declaração de "f int ()" é após a definição de "int main ()" :)
Sogartar
15
@ Sogartar, realmente? Os compiladores codepad.org/STSQlUhh C99 emitem um aviso, mas ainda permitem que você o compile.
jrajav
22
O @Sogartar nas funções C pode ser declarado implicitamente.
Alex B
11
@AlexB Não está em C99 e C11.
6
@jrajav Esses não são os compiladores C99, então. Um compilador C99 detecta identificadores não declarados como um erro de sintaxe. Um compilador que não faz isso é um compilador C89 ou um pré-padrão ou outro tipo de compilador não conforme.
430

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 ++.

int a = 10 //* comment */ 2 
        + 3;

No C ++, tudo, desde o //final da linha, é um comentário, portanto, funciona como:

int a = 10 + 3;

Como o C90 não possui comentários de linha única, apenas o /* comment */é um comentário. O primeiro /e o 2são ambos partes da inicialização, portanto, ele se propõe a:

int a = 10 / 2 + 3;

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.

Jerry Coffin
fonte
34
WHOA isso é alucinante !! De todas as coisas possíveis, eu nunca imaginaria que os comentários pudessem ser usados ​​para mudar o comportamento, haha. 1
user541686 15/10/12
89
mesmo sem o 2, seria lido como 10 / + 3válido (unário +).
Benoit
12
Agora, por diversão, modifique-o para que C e C ++ calculem expressões aritméticas diferentes e a avaliação para o mesmo resultado.
Ryan C. Thompson
21
@RyanThompson Trivial. s /
2/1
4
@Mehrdad Estou errado ou os comentários estão relacionados ao pré-processador? Portanto, eles devem ser excluídos como uma possível resposta da sua pergunta! ;-)
Ale
179

C90 vs. C ++ 11 ( intvs. double):

#include <stdio.h>

int main()
{
  auto j = 1.5;
  printf("%d", (int)sizeof(j));
  return 0;
}

Em C autosignifica variável local. No C90, não há problema em omitir variável ou tipo de função. O padrão é int. No C ++ 11 autosignifica algo completamente diferente, ele diz ao compilador para inferir o tipo da variável do valor usado para inicializá-la.

detunizado
fonte
10
C90 tem auto?
Seth Carnegie
22
@SethCarnegie: Sim, é uma classe de armazenamento; é o que acontece por padrão quando você o omite, então ninguém o usou e eles mudaram seu significado. Eu acho que é intpor padrão. Isso é inteligente! 1
user541686 15/12
5
C11 não tem implícito int.
R .. GitHub Pare de ajudar o gelo
23
@ KeithThompson Ah, eu acho que você quer dizer o inferido 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.
Jim Balter
11
"Toda variável DEVE ter uma classe de armazenamento explícita. Atenciosamente, gerência superior."
btown
120

Outro exemplo que ainda não vi mencionado, este destaca uma diferença de pré-processador:

#include <stdio.h>
int main()
{
#if true
    printf("true!\n");
#else
    printf("false!\n");
#endif
    return 0;
}

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.

godlygeek
fonte
2
Interessante. Alguém sabe a lógica por trás dessa mudança?
antred 2/09/14
3
porque "verdadeiro" é uma palavra-chave / valor válido, portanto é avaliado como verdadeiro como qualquer "valor verdadeiro" (como qualquer número inteiro positivo). Você ainda pode #define true false para imprimir "false" também em C ++;)
CoffeDeveloper
22
#define true false ಠ_ಠ
Bryan Boettcher
2
@DarioOO não resultará em redefinição em UB?
Ruslan
3
@DarioOO: Sim, você está errado. A redefinição de palavras-chave não é permitida, punição deixada ao destino (UB). O pré-processador é uma fase separada de compilação, não suporta.
Deduplicator
108

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 ++:

   char arr[100];
   int s = sizeof(0, arr);       // The comma operator is used.

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.

   enum E { a, b, c };
   sizeof(a) == sizeof(int);     // In C
   sizeof(a) == sizeof(E);       // In C++

Isso significa que sizeof(int)pode não ser igual a sizeof(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.

   int f();           // int f(void) in C++
                      // int f(*unknown*) in C
Kirill Kobelev
fonte
O primeiro também é definido como implementação, como o de Alexey. Mas +1.
Seth Carnegie
1
@Seth, Todo o material acima é retirado diretamente do Anexo C.1 da norma C ++ 11.
Kirill Kobelev
Sim, mas ainda está definido pela implementação. 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ção sfosse diferente, sacabaria 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.
Seth Carnegie
5
Existe uma solução fácil - basta alterar o exemplo para:char arr[sizeof(char*)+1]; int s = sizeof(0, arr);
Mankarse 15/10/12
5
Para evitar diferenças definidas pela implementação, você também pode usar 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.
finnw
53

Este programa imprime 1em C ++ e 0em C:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int d = (int)(abs(0.6) + 0.5);
    printf("%d", d);
    return 0;
}

Isso acontece porque há double abs(double)sobrecarga no C ++; portanto, abs(0.6)retorna 0.6enquanto em C ele retorna 0devido à conversão implícita de dupla para int antes de chamar int abs(int). Em C, você tem que usar fabspara trabalhar double.

Pavel Chikulaev
fonte
5
teve que depurar o código de outra pessoa com esse problema. Oh, como eu amei isso. De qualquer forma, seu programa também está imprimindo 0 em C ++. C ++ tem que usar o "cmath" cabeçalho veja a comparação de um primeiro regressando 0 ideone.com/0tQB2G segundo um retorno 1 ideone.com/SLeANo
CoffeDeveloper
Fico feliz em saber que não sou o único que encontra essa diferença via depuração. Apenas testado no VS2013, um vazio com apenas um arquivo com esse conteúdo produzirá 1 se a extensão for .cpp e 0 se a extensão for .c. Parece que <math.h> está incluído indiretamente no VS.
Pavel Chikulaev
E parece que no VS C ++, <math.h> inclui itens do C ++ no espaço de nomes global, onde, para o GCC, não é. No entanto, não sei qual é o comportamento padrão.
Pavel Chikulaev
2
Esse exemplo de código específico depende da implementação: stdlib.hdefine apenas abs(int)e abs(long); a versão abs(double)é declarada por math.h. Portanto, este programa ainda pode chamar a abs(int)versão. É um detalhe de implementação se stdlib.htambém faz math.hcom que seja incluído. (Eu acho que seria um bug se abs(double)fosse chamado, mas outros aspecs de math.hnão foram incluídos).
MM
1
Uma questão secundária é que, embora o padrão C ++ pareça dizer que incluir <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.
MM
38
#include <stdio.h>

int main(void)
{
    printf("%d\n", (int)sizeof('a'));
    return 0;
}

Em C, isso imprime qualquer valor que sizeof(int)esteja no sistema atual, que normalmente está 4na maioria dos sistemas atualmente em uso atualmente.

No C ++, isso deve imprimir 1.

Adam Rosenfield
fonte
3
Sim, eu estava familiarizado com esse truque, pois 'c' é um int em C e um caractere em C ++, mas ainda é bom tê-lo listado aqui.
23409 Sean
9
Isso faria uma pergunta da entrevista interessante - especialmente para as pessoas que colocam c / c ++ especialista em seus currículos
Martin Beckett
2
Tipo de disfarçado embora. O objetivo de sizeof é que você não precise saber exatamente o tamanho de um tipo.
Dana the Sane
13
Em C, o valor é definido pela implementação e 1 é uma possibilidade. (Em C ++ que tem para imprimir 1 como indicado.)
o Windows programador
3
Na verdade, ele tem um comportamento indefinido nos dois casos. %dnão é o especificador de formato certo para size_t.
R .. GitHub Pare de ajudar o gelo 27/02
37

Outra sizeofarmadilha: expressões booleanas.

#include <stdio.h>
int main() {
    printf("%d\n", (int)sizeof !0);
}

É igual a sizeof(int)C, porque a expressão é do tipo int, mas normalmente é 1 em C ++ (embora não seja necessário). Na prática, eles são quase sempre diferentes.

Alex B
fonte
6
Um !deve ser suficiente para a bool.
Alexey Frunze
4
!! é o operador de conversão booleana de int :)
#
1
sizeof(0)está 4em C e C ++ porque 0é um valor inteiro rvalue. sizeof(!0)está 4em C e 1em C ++. O lógico NOT opera em operandos do tipo bool. Se o valor int 0for implicitamente convertido em false(um valor booleano), será invertido, resultando em true. Ambos truee falsesão valores booleanos em C ++ e sizeof(bool)is 1. No entanto, em C é !0avaliado como 1, que é um rvalor do tipo int. A linguagem de programação C não possui tipo de dados bool por padrão.
Galaxy
26

A linguagem de programação C ++ (3ª edição) fornece três exemplos:

  1. sizeof ('a'), como @Adam Rosenfield mencionou;

  2. // comentários sendo usados ​​para criar código oculto:

    int f(int a, int b)
    {
        return a //* blah */ b
            ;
    }
  3. Estruturas etc. ocultando coisas em escopos externos, como no seu exemplo.

derobert
fonte
25

Uma castanha antiga que depende do compilador C, que não reconhece comentários de fim de linha em C ++ ...

...
int a = 4 //* */ 2
        +2;
printf("%i\n",a);
...
dmckee --- gatinho ex-moderador
fonte
21

Outro listado pelo padrão C ++:

#include <stdio.h>

int x[1];
int main(void) {
    struct x { int a[2]; };
    /* size of the array in C */
    /* size of the struct in C++ */
    printf("%d\n", (int)sizeof(x)); 
}
Johannes Schaub - litb
fonte
assim você obtém diferenças de preenchimento?
v.oddou
ah desculpa, entendi, tem outro xno topo. Eu pensei que você disse "a matriz a".
v.oddou
20

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

#include <stdio.h>

struct fun{};

int main()
{
    fun();  // In C, this calls the inline function from file 2 where as in C++
            // this would create a variable of struct fun
    return 0;
}

Arquivo 2

#include <stdio.h>
inline void fun(void)
{
    printf("I am inline\n");
} 

Além disso, o C ++ trata implicitamente qualquer constglobal como a staticmenos que seja declarado explicitamente extern, ao contrário de C no qual externé o padrão.

fkl
fonte
Eu realmente acho que não. Provavelmente você perdeu o objetivo. Não se trata da definição de struct st, que é apenas usada para tornar o código válido em c ++. O ponto é que destaca o comportamento diferente das funções embutidas em c vs c ++. O mesmo se aplica ao externo. Nada disso é discutido em nenhuma das soluções.
Fkl
2
Qual é o comportamento diferente das funções embutidas e externque é demonstrado aqui?
Seth Carnegie
Está escrito claramente. "Funções embutidas em c padrão para escopo externo, onde as que não estão em c ++ (o código mostra isso). O C ++ também trata implicitamente qualquer const global como escopo de arquivo, a menos que seja explicitamente declarado externo, diferentemente de C no qual extern é o padrão. exemplo pode ser criado para isso ". Estou intrigado - não é compreensível?
Fkl
12
@fayyazkl O comportamento mostrado é apenas devido à diferença de pesquisa ( struct funvs fn) e não tem nada a ver se a função está em linha. O resultado é idêntico se você remover o inlinequalificador.
Alex B
3
Na ISO C, este programa está mal formado: inlinenão foi adicionado até C99, mas na C99 fun()não pode ser chamado sem um protótipo no escopo. Então eu assumo esta resposta só se aplica a GNU C.
MM
16
struct abort
{
    int x;
};

int main()
{
    abort();
    return 0;
}

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:

struct exit
{
    int x;
};

int main()
{
    struct exit code;
    code.x=1;

    exit(code);

    return 0;
}

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
Seu segundo exemplo usando exit, não compila no gcc ou no g ++, infelizmente. É uma boa ideia, no entanto.
Sean
1
exit(code)é uma declaração válida de uma variável codedo tipo exit, aparentemente. (Consulte "análise mais irritante", que é um problema diferente, mas semelhante).
user253751
16
#include <stdio.h>

struct A {
    double a[32];
};

int main() {
    struct B {
        struct A {
            short a, b;
        } a;
    };
    printf("%d\n", sizeof(struct A));
    return 0;
}

Este programa imprime 128( 32 * sizeof(double)) quando compilado usando um compilador C ++ e 4quando 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.

wefwefa3
fonte
Este é interessante! (Eu acho que você quer dizer 32*sizeof(double), em vez de 32 embora :))
user1354557
3
nota que você está recebendo UB por imprimir size_tcom%d
phuclv
7

Não esqueça a distinção entre os namespaces globais C e C ++. Suponha que você tenha um foo.cpp

#include <cstdio>

void foo(int r)
{
  printf("I am C++\n");
}

e um foo2.c

#include <stdio.h>

void foo(int r)
{
  printf("I am C\n");
}

Agora, suponha que você tenha um main.c e um main.cpp parecidos com este:

extern void foo(int);

int main(void)
{
  foo(1);
  return 0;
}

Quando compilado como C ++, ele usará o símbolo no espaço para nome global C ++; em C usará o C:

$ diff main.cpp main.c
$ gcc -o test main.cpp foo.cpp foo2.c
$ ./test 
I am C++
$ gcc -o test main.c foo.cpp foo2.c
$ ./test 
I am C
user23614
fonte
Você quer dizer a especificação de ligação?
user541686
nome desconcertante. Nomes C ++ têm prefixos e sufixos, enquanto C não
CoffeDeveloper
A manipulação de nomes não faz parte da especificação C ++. É proibido em C?
SkyKing
5
Esse é um comportamento indefinido (definição múltipla de foo). Não existem "namespaces globais" separados.
MM
4
int main(void) {
    const int dim = 5; 
    int array[dim];
}

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 arraynão é uma expressão constante inteira em C. No C ++, o tipo é totalmente estático.


Se você tentar adicionar um inicializador aqui:

int main(void) {
    const int dim = 5; 
    int array[dim] = {0};
}

é C ++ válido, mas não C, porque matrizes de tamanho variável não podem ter um inicializador.

Antti Haapala
fonte
0

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:

int a = 5;
a++ = 2;  /* error: lvalue required as left operand of assignment */
++a = 2;  /* error: lvalue required as left operand of assignment */

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!

int a = 5;
a++ = 2;  // error: lvalue required as left operand of assignment
++a = 2;  // No error: a gets assigned to 2!

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.

int x = a;
int x = ++a;

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.

int a;
a = 2;
++a = 2;  // Valid in C++.
Galáxia
fonte
3
Não há "válido em C" aqui.
o11c 27/11/18
0

Estruturas vazias têm tamanho 0 em C e 1 em C ++:

#include <stdio.h>

typedef struct {} Foo;

int main()
{
    printf("%zd\n", sizeof(Foo));
    return 0;
}
Daniel Frużyński
fonte
1
Não, a diferença é que C faz não têm estruturas vazias, exceto como uma extensão de compilador, ou seja, este código não corresponde "é válida em C e C ++"
Antti Haapala