Los lenguajes de programación C y C++ están estrechamente relacionados, pero tienen muchas diferencias significativas. C++ comenzó como una bifurcación de un C temprano, pre- estandarizado , y fue diseñado para ser mayormente compatible en código fuente y enlace con los compiladores de C de la época. [1] [2] Debido a esto, las herramientas de desarrollo para los dos lenguajes (como IDE y compiladores ) a menudo se integran en un solo producto, y el programador puede especificar C o C++ como su lenguaje fuente.
Sin embargo, C no es un subconjunto de C++, [3] y los programas C no triviales no se compilarán como código C++ sin modificaciones. Asimismo, C++ presenta muchas características que no están disponibles en C y en la práctica casi todo el código escrito en C++ no es código C conforme. Este artículo, sin embargo, se centra en las diferencias que hacen que el código C conforme sea código C++ mal formado, o que sea conforme/bien formado en ambos lenguajes pero que se comporte de manera diferente en C y C++.
Bjarne Stroustrup , el creador de C++, ha sugerido [4] que las incompatibilidades entre C y C++ deberían reducirse tanto como sea posible para maximizar la interoperabilidad entre los dos lenguajes. Otros han argumentado que, dado que C y C++ son dos lenguajes diferentes, la compatibilidad entre ellos es útil pero no vital; según este grupo, los esfuerzos por reducir la incompatibilidad no deberían obstaculizar los intentos de mejorar cada lenguaje de forma aislada. La justificación oficial del estándar C de 1999 ( C99 ) "respaldó el principio de mantener el subconjunto común más grande" entre C y C++ "mientras se mantiene una distinción entre ellos y se les permite evolucionar por separado", y declaró que los autores estaban "contentos con dejar que C++ sea el lenguaje grande y ambicioso". [5]
Varias incorporaciones de C99 no son compatibles con el estándar C++ actual o entran en conflicto con características de C++, como las matrices de longitud variable , los tipos de números complejos nativos y el restrict
calificador de tipo . Por otra parte, C99 redujo algunas otras incompatibilidades en comparación con C89 al incorporar características de C++ como //
comentarios y declaraciones y código mixtos. [6]
C++ aplica reglas de tipado más estrictas (no se permiten violaciones implícitas del sistema de tipos estáticos [1] ) y requisitos de inicialización (aplicación en tiempo de compilación de que las variables dentro del ámbito no tengan una inicialización subvertida) [7] que C, por lo que parte del código C válido no es válido en C++. En el Anexo C.1 del estándar ISO C++ se ofrece una justificación de esto. [8]
void*
asignar un puntero a cualquier tipo de puntero sin una conversión, mientras que C++ no lo permite; este modismo aparece a menudo en el código C que utiliza malloc
la asignación de memoria, [9] o en el paso de punteros de contexto a la API pthreads de POSIX y otros marcos que implican devoluciones de llamadas . Por ejemplo, lo siguiente es válido en C pero no en C++:void * ptr ; /* Conversión implícita de void* a int* */ int * i = ptr ;
o de manera similar:
int * j = malloc ( 5 * sizeof * j ); /* Conversión implícita de void* a int* */
Para que el código se compile tanto como C como C++, se debe utilizar una conversión explícita, como se muestra a continuación (con algunas salvedades en ambos lenguajes [10] [11] ):
vacío * ptr ; int * i = ( int * ) ptr ; int * j = ( int * ) malloc ( 5 * tamaño de * j );
int **
a const int *const *
pero no la asignación insegura a, const int **
mientras que C no permite ninguna de ellas (aunque los compiladores normalmente solo emitirán una advertencia).const
calificadores de tipo , por ejemplo, strchr
devuelve char*
en C, mientras que C++ actúa como si hubiera dos funciones sobrecargadas const char *strchr(const char *)
y una char *strchr(char *)
. En C23 se utilizará la selección genérica para hacer que el comportamiento de C sea más similar al de C++. [12]enum
enumeradores) siempre son de tipo int
en C, mientras que son tipos distintos en C++ y pueden tener un tamaño diferente al de int
. [ necesita actualización ]const
se debe inicializar una variable; en C esto no es necesario.void fn ( void ) { ir a flack ; int i = 1 ; flack : ; }
longjmp()
saltados incluyen objetos con destructores no triviales. [13] La implementación de C++ es libre de definir el comportamiento de modo que se llamen a los destructores. Sin embargo, esto impediría algunos usos de los cuales de otra manera serían válidos, como la implementación de subprocesos o corrutinas que cambian entre pilas de llamadas separadas con — al saltar de la pila de llamadas inferior a la superior en el espacio de direcciones global, se llamarían a los destructores para cada objeto en la pila de llamadas inferior. No existe tal problema en C.longjmp()
longjmp()
int N ; int N = 10 ;
struct
, union
o enum
, pero no es válido en C++, porque en C, struct
los tipos union
, y enum
deben indicarse como tales siempre que se haga referencia al tipo, mientras que en C++ todas las declaraciones de dichos tipos llevan el typedef implícitamente.enum BOOL { FALSO , VERDADERO }; typedef int BOOL ;
int foo();
., implica que los parámetros no están especificados. Por lo tanto, es legal llamar a dicha función con uno o más argumentos , p. ej foo(42, "hello world")
., . Por el contrario, en C++ un prototipo de función sin argumentos significa que la función no toma argumentos, y llamar a dicha función con argumentos está mal formado. En C, la forma correcta de declarar una función que no toma argumentos es usando 'void', como en int foo(void);
, que también es válido en C++. Los prototipos de función vacíos son una característica obsoleta en C99 (como lo fueron en C89).struct
tipos anidados, pero el alcance se interpreta de manera diferente: en C++, un tipo anidado struct
se define solo dentro del alcance/espacio de nombres de la estructura externa struct
, mientras que en C la estructura interna también se define fuera de la estructura externa.struct
, union
, y enum
se declaren en prototipos de funciones, mientras que C++ no lo permite.C99 y C11 agregaron varias características adicionales a C que no se habían incorporado al C++ estándar a partir de C++20 , como números complejos, matrices de longitud variable (los números complejos y las matrices de longitud variable se designan como extensiones opcionales en C11), miembros de matriz flexibles , la palabra clave restrict , calificadores de parámetros de matriz y literales compuestos .
float complex
y double complex
se agregó en el estándar C99_Complex
, a través de la palabra clave y complex
la macro de conveniencia. En C++, la aritmética compleja se puede realizar utilizando la clase de número complejo, pero los dos métodos no son compatibles en cuanto al código. (Sin embargo, los estándares posteriores a C++11 requieren compatibilidad binaria.) [17]void foo ( size_t x , int a [ * ]); // Declaración VLA void foo ( size_t x , int a [ x ]) { printf ( "%zu \n " , sizeof a ); // igual que sizeof(int*) char s [ x * 2 ]; printf ( "%zu \n " , sizeof s ); // imprimirá x*2 }
estructura X { int n , m ; char bytes []; }
restrict
calificador de tipo definido en C99 no se incluyó en el estándar C++03, pero la mayoría de los compiladores convencionales, como GNU Compiler Collection , [19] Microsoft Visual C++ e Intel C++ Compiler, proporcionan una funcionalidad similar como extensión.int foo ( int a [ const ]); // equivalente a int *const a int bar ( char s [ static 5 ]); // anota que s tiene al menos 5 caracteres de longitud
struct X a = ( struct X ){ 4 , 6 }; // El equivalente en C++ sería X{4, 6}. La forma sintáctica de C utilizada en C99 se admite como una extensión en los compiladores GCC y Clang C++. foo ( & ( struct X ){ 4 , 6 }); // El objeto se asigna en la pila y su dirección se puede pasar a una función. Esto no se admite en C++. if ( memcmp ( d , ( int []){ 8 , 6 , 7 , 5 , 3 , 0 , 9 }, n ) == 0 ) {} // El equivalente en C++ sería usar digits = int []; if (memcmp(d, digits{8, 6, 7, 5, 3, 0, 9}, n) == 0) {}
char s [ 20 ] = { [ 0 ] = 'a' , [ 8 ] = 'g' }; // permitido en C, no en C++
noreturn
atributo, mientras que C utiliza una palabra clave distinct. En C23, también se admite la sintaxis de atributo. [20]C++ agrega numerosas palabras clave adicionales para respaldar sus nuevas funciones. Esto hace que el código C que utilice esas palabras clave como identificadores no sea válido en C++. Por ejemplo:
plantilla de estructura { int nuevo ; plantilla de estructura * clase ; };
template
y están reservadas new
.class
Hay algunas construcciones sintácticas que son válidas tanto en C como en C++ pero producen resultados diferentes en los dos lenguajes.
'a'
son de tipo int
en C y de tipo char
en C++, lo que significa que sizeof 'a'
generalmente darán resultados diferentes en los dos lenguajes: en C++, será 1
, mientras que en C será sizeof(int)
. Como otra consecuencia de esta diferencia de tipo, en C, 'a'
siempre será una expresión con signo, independientemente de si char
es un tipo con signo o sin signo, mientras que para C++ esto es específico de la implementación del compilador.const
a menos que se declaren explícitamente extern
, a diferencia de C, en el que extern
es el valor predeterminado para todas las entidades con ámbito de archivo. En la práctica, esto no conduce a cambios semánticos silenciosos entre código idéntico de C y C++, sino que conducirá a un error de tiempo de compilación o de enlace.inline
funciones : definiciones externas ordinarias (donde extern
se usa explícitamente) y definiciones en línea. C++, por otro lado, proporciona solo definiciones en línea para funciones en línea. En C, una definición en línea es similar a una interna (es decir, estática), en el sentido de que puede coexistir en el mismo programa con una definición externa y cualquier número de definiciones internas y en línea de la misma función en otras unidades de traducción, todas las cuales pueden diferir. Esta es una consideración separada del enlace de la función, pero no independiente. Los compiladores de C tienen la discreción de elegir entre usar definiciones en línea y externas de la misma función cuando ambas son visibles. Sin embargo, C++ requiere que si se declara una función con enlace externo inline
en cualquier unidad de traducción, entonces debe declararse de esa manera (y, por lo tanto, también definirse) en cada unidad de traducción donde se utilice, y que todas las definiciones de esa función sean idénticas, siguiendo el ODR. Las funciones estáticas en línea se comportan de manera idéntica en C y C++.bool
con constantes true
y false
, pero se definen de forma diferente. En C++, bool
es un tipo integrado y una palabra clave reservada . En C99, _Bool
se introduce una nueva palabra clave, , como el nuevo tipo booleano. El encabezado stdbool.h
proporciona macros bool
, true
y false
que se definen como _Bool
, 1
y 0
, respectivamente. Por lo tanto, true
y false
tienen tipo int
en C. Sin embargo, es probable que esto cambie en C23 , cuyo borrador incluye cambiar bool
, true
, y false
para que se conviertan en palabras clave, y dar true
y false
el tipo bool
.int
tiene signo o no, mientras que en C++ siempre tiene signo para que coincida con el tipo subyacente.Varias de las otras diferencias con respecto a la sección anterior también se pueden aprovechar para crear código que se compile en ambos lenguajes pero que se comporte de manera diferente. Por ejemplo, la siguiente función devolverá valores diferentes en C y C++:
externo int T ; int tamaño ( void ) { struct T { int i ; int j ; }; devolver tamañode ( T ); /* C: devolver tamañode(int) * C++: devolver tamañode(struct T) */ }
Esto se debe a que C requiere struct
delante de las etiquetas de estructura (y por lo tanto sizeof(T)
se refiere a la variable), pero C++ permite que se omita (y por lo tanto sizeof(T)
se refiere al implícito typedef
). Tenga en cuenta que el resultado es diferente cuando la extern
declaración se coloca dentro de la función: entonces, la presencia de un identificador con el mismo nombre en el ámbito de la función inhibe que el implícito typedef
surta efecto para C++, y el resultado para C y C++ sería el mismo. Observe también que la ambigüedad en el ejemplo anterior se debe al uso del paréntesis con el sizeof
operador. El uso sizeof T
esperaría T
ser una expresión y no un tipo, y por lo tanto, el ejemplo no se compilaría con C++.
Si bien C y C++ mantienen un alto grado de compatibilidad de código fuente, los archivos de objetos que producen sus respectivos compiladores pueden tener diferencias importantes que se manifiestan al mezclar código C y C++. En particular:
Por estos motivos, para que el código C++ llame a una función C foo()
, el código C++ debe crear un prototipo foo()
con extern "C"
. Del mismo modo, para que el código C llame a una función C++ bar()
, el código C++ para bar()
debe declararse con extern "C"
.
Una práctica común para que los archivos de encabezado mantengan la compatibilidad con C y C++ es hacer que su declaración sea extern "C"
para el alcance del encabezado: [22]
/* Archivo de encabezado foo.h */ #ifdef __cplusplus /* Si se trata de un compilador de C++, utilice el enlace C */ extern "C" { #endif /* Estas funciones obtienen enlace C */ void foo (); struct bar { /* ... */ }; #ifdef __cplusplus /* Si este es un compilador de C++, finaliza el enlace de C */ } #endif
Las diferencias entre las convenciones de vinculación y llamada de C y C++ también pueden tener implicaciones sutiles para el código que utiliza punteros de función . Algunos compiladores producirán código que no funciona si un puntero de función declarado extern "C"
apunta a una función de C++ que no está declarada extern "C"
. [23]
Por ejemplo, el siguiente código:
void mi_funcion (); externo "C" void foo ( void ( * fn_ptr )( void )); barra vacía () { foo ( mi_funcion );}
Al utilizar el compilador C++ de Sun Microsystems , esto produce la siguiente advertencia:
$ CC - c test . cc "test.cc" , línea 6 : Advertencia ( anacronismo ) : El argumento formal fn_ptr del tipo extern "C" void ( * )() en la llamada a foo ( extern "C" void ( * )()) se está pasando void ( * )().
Esto se debe a que my_function()
no se declara con las convenciones de llamada y enlace de C, sino que se pasa a la función de foo()
C.