tipos - ¿Puede el código que es válido en C y C++ producir un comportamiento diferente cuando se compila en cada idioma?




tipos de codigos linguisticos (12)

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

#include <stdio.h>

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

En C auto significa variable local. En C90 está bien omitir el tipo de variable o función. El valor predeterminado es int . En C ++ 11 auto significa algo completamente diferente, le dice al compilador que deduzca el tipo de variable del valor usado para inicializarlo.

C y C ++ tienen muchas diferencias, y no todos los códigos C válidos son códigos C ++ válidos.
(Por "válido" me refiero a un código estándar con comportamiento definido, es decir, no específico de la implementación / indefinido / etc.)

¿Hay algún escenario en el que un fragmento de código válido en C y C ++ produzca un comportamiento diferente cuando se compila con un compilador estándar en cada idioma?

Para hacer una comparación razonable / útil (estoy tratando de aprender algo práctico útil, no tratar de encontrar lagunas evidentes en la pregunta), asumamos:

  • Nada relacionado con el preprocesador (lo que significa que no hay #ifdef __cplusplus con #ifdef __cplusplus , pragmas, etc.)
  • Cualquier cosa definida por la implementación es la misma en ambos idiomas (por ejemplo, límites numéricos, etc.)
  • Estamos comparando versiones razonablemente recientes de cada estándar (por ejemplo, C ++ 98 y C90 o posterior)
    Si las versiones son importantes, mencione qué versiones de cada una producen un comportamiento diferente.

Este es un ejemplo que aprovecha la diferencia entre las llamadas a funciones y las declaraciones de objetos en C y C ++, así como el hecho de que C90 permite la llamada de funciones no declaradas:

#include <stdio.h>

struct f { int x; };

int main() {
    f();
}

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

En C ++ esto no imprimirá nada porque se crea y destruye una f temporal, pero en C90 imprime hello porque las funciones se pueden llamar sin haber sido declaradas.

En caso de que se esté preguntando si el nombre f se usa dos veces, los estándares C y C ++ lo permiten explícitamente, y para hacer que un objeto tenga que decir la struct f a la ambigüedad si desea la estructura, o deje de struct la struct si desea que la función .


Esto se refiere a los valores de l y rvalues ​​en C y C ++.

En el lenguaje de programación C, tanto el pre-incremento como los operadores post-incremento devuelven valores, no valores. Esto significa que no pueden estar en el lado izquierdo del operador de asignación = . Ambas declaraciones darán un error de compilación en C:

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

En C ++, sin embargo, el operador de preincremento devuelve un lvalue, mientras que el operador de postincremento devuelve un rvalue. ¡Significa que una expresión con el operador de preincremento se puede colocar en el lado izquierdo del operador de asignación = !

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

Ahora por qué esto es así? El incremento posterior incrementa la variable, y devuelve la variable como estaba antes de que ocurriera el incremento. Esto es en realidad sólo un valor. El valor anterior de la variable a se copia en un registro como temporal, y luego se incrementa a. Pero el valor anterior de a es devuelto por la expresión, es un valor de r. Ya no representa el contenido actual de la variable.

El pre-incremento primero incrementa la variable, y luego retorna la variable como se hizo después de que ocurrió el incremento. En este caso, no necesitamos almacenar el valor antiguo de la variable en un registro temporal. Simplemente recuperamos el nuevo valor de la variable después de que se haya incrementado. Entonces, el pre-incremento devuelve un lvalor, retorna la variable a sí misma. Podemos usar asignar este valor límico a otra cosa, es como la siguiente declaración. Esta es una conversión implícita de lvalue en rvalue.

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

Dado que el pre-incremento devuelve un lvalor, también podemos asignarle algo. Las siguientes dos afirmaciones son idénticas. En la segunda asignación, primero se incrementa a, luego se sobrescribe su nuevo valor con 2.

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

Las estructuras vacías tienen tamaño 0 en C y 1 en C ++:

#include <stdio.h>

typedef struct {} Foo;

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

Lo siguiente, válido en C y C ++, dará como resultado (lo más probable) resultados diferentes en i en C y C ++:

int i = sizeof('a');

Consulte Tamaño del carácter ('a') en C / C ++ para obtener una explicación de la diferencia.

Otro de este artículo :

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

No olvide la distinción entre los espacios de nombres globales de C y C ++. Supongamos que tienes un foo.cpp

#include <cstdio>

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

y un foo2.c

#include <stdio.h>

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

Ahora suponga que tiene un main.c y main.cpp que se ven así:

extern void foo(int);

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

Cuando se compila como C ++, usará el símbolo en el espacio de nombres global de C ++; en C utilizará la C one:

$ 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

Otro ejemplo que no he visto mencionado aún, este destaca una diferencia de preprocesador:

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

Esto imprime "falso" en C y "verdadero" en C ++: en C, cualquier macro indefinida se evalúa como 0. En C ++, hay 1 excepción: "verdadero" se evalúa como 1.


Otro listado por el estándar de 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)); 
}

Según el estándar C ++ 11:

a. El operador de coma realiza la conversión de valor a valor en C pero no en C ++:

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

En C ++, el valor de esta expresión será 100 y en C será sizeof(char*) .

segundo. En C ++ el tipo de enumerador es su enumeración. En C el tipo de enumerador es int.

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

Esto significa que sizeof(int) puede no ser igual a sizeof(E) .

do. En C ++, una función declarada con una lista de parámetros vacía no toma argumentos. En C, la lista de parámetros vacía significa que se desconoce el número y el tipo de función params.

   int f();           // int f(void) in C++
                      // int f(*unknown*) in C

Una castaña antigua que depende del compilador de C, que no reconoce los comentarios de fin de línea de C ++ ...

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

#include <stdio.h>

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

En C, esto imprime cualquiera que sea el valor de sizeof(int) en el sistema actual, que suele ser 4 en la mayoría de los sistemas que se usan comúnmente en la actualidad.

En C ++, esto debe imprimirse 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;
}

Este programa imprime 128 ( 32 * sizeof(double) ) cuando se compila con un compilador de C ++ y 4 cuando se compila con un compilador de C.

Esto se debe a que C no tiene la noción de resolución de alcance. En C, las estructuras contenidas en otras estructuras se ponen dentro del alcance de la estructura exterior.







c