tutorial - Il codice valido sia in C che in C++ produce un comportamento diverso quando viene compilato in ogni lingua?




use c++ code in c (12)

C e C ++ hanno molte differenze e non tutto il codice C valido è un codice C ++ valido.
(Con "valido" intendo codice standard con comportamento definito, cioè non specifico dell'implementazione / indefinito / ecc.)

C'è qualche scenario in cui un pezzo di codice valido sia in C che in C ++ produrrebbe un comportamento diverso quando compilato con un compilatore standard in ogni lingua?

Per fare un confronto ragionevole / utile (sto cercando di imparare qualcosa di praticamente utile, non per cercare di trovare scappatoie evidenti nella domanda), assumiamo:

  • Niente di relativo al preprocessore (che significa no hack con #ifdef __cplusplus , pragma, ecc.)
  • Qualunque implementazione definita è la stessa in entrambe le lingue (ad es. Limiti numerici, ecc.)
  • Stiamo confrontando versioni ragionevolmente recenti di ogni standard (ad esempio, C ++ 98 e C90 o successivi)
    Se le versioni sono importanti, ti preghiamo di indicare quali versioni di ciascuna producono un comportamento diverso.

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

#include <stdio.h>

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

In C auto significa variabile locale. In C90 va bene omettere la variabile o il tipo di funzione. Il valore predefinito è int . In C ++ 11 auto significa qualcosa di completamente diverso, dice al compilatore di inferire il tipo di variabile dal valore usato per inizializzarlo.


Ecco un esempio che sfrutta la differenza tra chiamate di funzione e dichiarazioni di oggetti in C e C ++, nonché il fatto che C90 consente la chiamata di funzioni non dichiarate:

#include <stdio.h>

struct f { int x; };

int main() {
    f();
}

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

In C ++ questo non stampa nulla perché una f temporanea viene creata e distrutta, ma in C90 stamperà hello perché le funzioni possono essere chiamate senza essere state dichiarate.

Nel caso ti stavi chiedendo se il nome f sia usato due volte, gli standard C e C ++ lo consentono esplicitamente, e per fare un oggetto devi dire struct f per disambiguare se vuoi la struttura, o lasciare struct se vuoi la funzione .


Le strutture vuote hanno dimensione 0 in C e 1 in C ++:

#include <stdio.h>

typedef struct {} Foo;

int main()
{
    printf("%zd\n", sizeof(Foo));
    return 0;
}

Non dimenticare la distinzione tra gli spazi dei nomi globali C e C ++. Supponiamo di avere un foo.cpp

#include <cstdio>

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

e un foo2.c

#include <stdio.h>

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

Ora supponiamo di avere un main.c e main.cpp che assomigliano a questo:

extern void foo(int);

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

Quando compilato come C ++, utilizzerà il simbolo nello spazio dei nomi globale C ++; in C userà il 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

Per C ++ vs. C90, c'è almeno un modo per ottenere un comportamento diverso che non è definito dall'implementazione. C90 non ha commenti a riga singola. Con un po 'di attenzione, possiamo usarlo per creare un'espressione con risultati completamente diversi in C90 e in C ++.

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

In C ++, tutto dal // alla fine della riga è un commento, quindi questo funziona come:

int a = 10 + 3;

Poiché C90 non ha commenti a riga singola, solo il /* comment */ è un commento. Il primo / e il 2 sono entrambi parti dell'inizializzazione, quindi esce a:

int a = 10 / 2 + 3;

Quindi, un compilatore C ++ corretto darà 13, ma un compilatore C corretto 8. Ovviamente, ho appena selezionato numeri arbitrari qui - puoi usare altri numeri come meglio credi.


Quanto segue, valido in C e C ++, porterà (molto probabilmente) a valori diversi in i in C e C ++:

int i = sizeof('a');

Vedi Dimensione del carattere ('a') in C / C ++ per una spiegazione della differenza.

Un altro da questo articolo :

#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;
}

Questo riguarda lvalues ​​e rvalues ​​in C e C ++.

Nel linguaggio di programmazione C, sia gli operatori di pre-incremento sia quelli di post-incremento restituiscono rvalues, non lvalue. Ciò significa che non possono trovarsi sul lato sinistro dell'operatore = assignment. Entrambe queste affermazioni daranno un errore del compilatore in C:

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

In C ++, tuttavia, l'operatore di pre-incremento restituisce un lvalue , mentre l'operatore di post-incremento restituisce un valore. Significa che un'espressione con l'operatore di pre-incremento può essere posizionata sul lato sinistro dell'operatore = assegnazione!

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

Ora, perché è così? Il post-incremento incrementa la variabile e restituisce la variabile com'era prima che si verificasse l'incremento. Questo è in realtà solo un valore. Il precedente valore della variabile a viene copiato in un registro come temporaneo e quindi a viene incrementato. Ma il valore precedente di a viene restituito dall'espressione, è un valore. Non rappresenta più il contenuto corrente della variabile.

Il pre-incremento incrementa prima la variabile, quindi restituisce la variabile come è diventata dopo che è avvenuto l'incremento. In questo caso, non è necessario memorizzare il vecchio valore della variabile in un registro temporaneo. Abbiamo appena recuperato il nuovo valore della variabile dopo che è stato incrementato. Quindi il pre-incremento restituisce un valore, restituisce la variabile a se stesso. Possiamo usare assegnare questo lvalue a qualcos'altro, è come la seguente dichiarazione. Questa è una conversione implicita di lvalue in rvalue.

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

Poiché il pre-incremento restituisce un valore, possiamo anche assegnargli qualcosa. Le seguenti due affermazioni sono identiche. Nel secondo compito, prima a viene incrementato, quindi il suo nuovo valore viene sovrascritto con 2.

int a;
a = 2;
++a = 2;  // Valid in C++.

Un altro elencato dallo standard 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)); 
}

Un'altra trappola di dimensioni: espressioni booleane.

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

È uguale a sizeof(int) in C, perché l'espressione è di tipo int , ma è in genere 1 in C ++ (anche se non è obbligatorio esserlo). In pratica sono quasi sempre diversi.


Una vecchia castagna che dipende dal compilatore C, non riconoscendo i commenti di fine linea C ++ ...

...
int a = 4 //* */ 2
        +2;
printf("%i\n",a);
...

#include <stdio.h>

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

In C, questo stampa qualunque sia il valore di sizeof(int) trova sul sistema corrente, che è in genere 4 nella maggior parte dei sistemi comunemente in uso oggi.

In C ++, questo deve stampare 1.


#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;
}

Questo programma stampa 128 ( 32 * sizeof(double) ) quando compilato usando un compilatore C ++ e 4 quando compilato usando un compilatore C.

Questo perché C non ha la nozione di risoluzione dell'ambito. In C strutture contenute in altre strutture vengono inserite nell'ambito della struttura esterna.







c