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. Fue diseñado para ser compatible en su mayor parte en cuanto a código fuente y enlace con los compiladores de C de la época.

La idea era crear un superconjunto de C. 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++, y programas C no triviales no se compilarán como código C++ sin modificaciones. Asimismo, C++ introduce 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 legal. Este artículo, sin embargo, se centra en las diferencias que hacen que código correcto en C sea código C++ mal formado, o que sea conforme/esté bien formado en ambos lenguajes pero se comporte de manera diferente en C y C++.

Bjarne Stroustrup, el creador de C++, ha expresado 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 independientemente. La justificación oficial del estándar C de 1999 (C99) "respalda el principio de mantener el subconjunto común más grande posible" entre C y C++" mientras se mantiene la distinción entre ellos y se les permite evolucionar por separado", y afirma que los autores estaban "contentos de dejar que C++ sea un lenguaje grande y ambicioso".

Varias adiciones de C99 no son compatibles con el estándar C++ actual o entran en conflicto con características de C++, como los arrays de longitud variable, tipos de números complejos nativos y el calificador de tipo restrict . Por otra parte, C99 redujo algunas otras incompatibilidades en comparación con C89 al incorporar características de C++ como comentarios de una sola línea // y declaraciones y código mixtos.

Construcciones válidas en C pero no en C++

C++ aplica reglas de tipado más estrictas (sin violaciones implícitas del sistema de tipos estático ) y requisitos de inicialización (comprobación en tiempo de compilación de que las variables dentro del ámbito no tengan la inicialización subvertida) ("It is invalid to jump past a declaration with explicit or implicit initializer (except across entire block not entered). … With this simple compile-time rule, C++ assures that if an initialized variable is in scope, then it has assuredly been initialized.") que C, por lo que parte del código C válido es inválido en C++. En el Anexo C.1 del estándar ISO C++ se ofrece una justificación de esto. Una diferencia notable es que C aplica un tipado más suave con respecto a los punteros. En concreto, C permite asignar un puntero de tipo void* a un puntero de cualquier tipo, sin un cast explícito. C++ no permite esto. Esta construcción aparece a menudo en código C cuando se utiliza la función malloc para alojar memoria. Por ejemplo, el siguiente código en válido en C, pero no en C++.void *ptr; /* Conversión implícita de void* a int* */ int *i = ptr;o similarmente:int *j = malloc(5 * sizeof *j); /* Conversión implícita de void* a int* */Para que este código sea válido en ambos lenguajes, se debe añadir un cast explícito.void *ptr; int *i = (int *)ptr; int *j = (int *)malloc(5 * sizeof *j);

C++ tiene reglas más complicadas sobre la asignación de punteros que añade calificadores. Por ejemplo, permite la asignación de int** a int *const *, pero no a const **int. C no permite ninguna de las 2, pero los compiladores normalmente solo emiten un warning.

C++ cambia algunas funciones de la librería estándar de C , para añadir funciones sobrecargadas que aceptan argumentos calificados como const. Por ejemplo, la función strchr recibe un argumento de tipo char*, y devuelve char*, mientras que en C++, son dos funciones sobrecargadas que pueden recibir char* o const char*. En C23, se añade selección genérica para hacer que C se comporte de forma más similar a C++.

C++ también es más estricto respecto a la conversión entre ints y enumerados. Los enteros no pueden ser convertidos de forma implícita a enums, como en C, y son tipos independientes, en lugar de ser representados como enteros, por lo que podrían tener un tamaño diferente.

En C++, una variable constante debe ser inicializada, mientras que en C no es necesario.

C++ prohíbe que las expresiones goto y switch crucen una inicialización, como en el siguiente bloque de código C: void fn(void) { goto flack; int i = 1; flack:

Aunque es sintácticamente válido, un longjmp(), resulta en comportamiento indefinido (UB) si los marcos de pila sobre los que se salta contienen objetos con destructores no triviales. Cada implementación de C++ es libre de definir el comportamiento de modo que se llame a los destructores. Sin embargo, esto impediría algunos usos de longjmp() que de otro modo serían válidos, como la implementación de hilos o corrutinas que cambian entre pilas de llamadas separadas con longjmp(): al saltar de la pila de llamadas inferior a la superior en el espacio de direcciones global, se llamaría a los destructores para cada objeto en la pila de llamadas inferior. No existe tal problema en C.

C permite múltiples declaraciones de una misma variable global en una misma unidad de traducción, lo cual es ilegal en C++. int N; int N = 10;

En C, definir un nuevo tipo con el mismo nombre que el de un struct, union o enum existente es válido, pero no lo es en C++. Esto es porque en C++, todas las declaraciones de struct, union y enum, tienen el typedef de forma implícita.

Ejemplo:enum BOOL {FALSE, TRUE}; typedef int BOOL;

Las declaraciones de función que no son prototipo (estilo "K&R") no son válidas en C++; siguen siendo válidos en C hasta C23, aunque se han considerado obsoletas desde la estandarización original de C en 1990. (El término "obsoleto" es un término definido en la norma ISO C, es decir, una característica que "puede ser considerada para su retirada en futuras revisiones" de la norma). Del mismo modo, las declaraciones de funciones implícitas (que usan funciones que no se han declarado) no están permitidas en C++ y no son válidas en C desde 1999.

En C hasta C23, una declaración de función sin parámetros, por ejemplo, int foo(), implica que los parámetros no están especificados. Por lo tanto, es legal llamar a tal función con uno o más argumentos, por ejemplo, foo(42, "hola mundo"). Por el contrario, en C++ un prototipo de función sin argumentos significa que la función no toma argumentos, y llamar a una función de este tipo con argumentos está prohibido. 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 funciones vacías son una característica obsoleta en C99 (como lo eran en C89).

Tanto en C como en C++, se pueden definir struct anidados, pero el ámbito se interpreta de forma diferente. En C++, una estructura anidada solo se define dentro del ámbito/espacio de nombres de la estructura externa, mientras que en C la estructura interna también se define fuera de la estructura externa.

C permite la definición de struct, union, y enum en prototipos de funciones, mientras que C++ no.

C99 y C11 agregaron varias características adicionales a C que no se habían incorporado al estándar de C++ a partir de C++20, como números complejos, arrays de longitud variable (los números complejos y los arrays de longitud variable se designan como extensiones opcionales en C11), miembros de array flexibles, la palabra clave restrict, calificadores de parámetros de array y literales compuestos . La aritmética compleja que utiliza los tipos de datos primitivos float complex y double complex se agregó en el estándar C99, a través de la palabra clave _Complex. En C++, la aritmética compleja se puede realizar mediante la clase numérica complex, pero los dos métodos no son compatibles con el código. (Sin embargo, los estándares desde C++11 requieren compatibilidad binaria).

Arrays de longitud variable (VLA). Esta característica abre la posibilidad de un operador sizeof que no se ejecute en tiempo de compilación.void foo(size_t x, int a[*]); // VLA declaration void foo(size_t x, int a[x]) { printf("%zu\n", sizeof a); // same as sizeof(int*) char s[x * 2]; printf("%zu\n", sizeof s); // will print x*2 }

En C99, el último miembro de un struct con más de un miembro puede ser un miembro de array flexible, que adopta la forma sintáctica de un array con una longitud no especificada. Esto tiene un propósito similar a arrays de longitud variable, pero los VLA no pueden aparecer en las definiciones de tipo y, a diferencia de los VLA, los miembros de array flexibles no tienen un tamaño definido. ISO C++ no tiene tal característica. Ejemplo:struct X { int n, m; char bytes[]; }

El calificador de tipo restrict, definido en C99, no se incluyó en el estándar C++03, pero la mayoría de los compiladores convencionales, como GNU Compiler Collection,Restricted Pointers from Using the GNU Compiler Collection (GCC) Microsoft Visual C++ e Intel C++ Compiler proporcionan una funcionalidad similar como extensión.

Los calificadores de parámetros de array en funciones se admiten en C, pero no en C++. int foo(int a[const]);     // Equivalente a int *const a int bar(char s[static 5]); // Anotación que expresa que s es de longitud 5

La funcionalidad de los literales compuestos en C se generaliza tanto a los tipos incorporados como a los definidos por el usuario, mediante la sintaxis de inicialización de lista de C++11, aunque con algunas diferencias sintácticas y semánticas. struct X a = (struct X){4, 6}; // The equivalent in C++ would be X{4, 6}. The C syntactic form used in C99 is supported as an extension in the GCC and Clang C++ compilers. foo(&(struct X){4, 6}); // The object is allocated in the stack and its address can be passed to a function. This is not supported in C++.

if (memcmp(d, (int []){8, 6, 7, 5, 3, 0, 9}, n) == 0) {} // The equivalent in C++ would be using digits = int []; if (memcmp(d, digits{8, 6, 7, 5, 3, 0, 9}, n) == 0) {}

Los inicializadores designados, en arrays, solo son válidos en C: char s = { = 'a', = 'g' }; // allowed in C, not in C++

Las funciones que no terminan se pueden anotar mediante un atributo noreturn en C++, mientras que C utiliza una palabra clave distinta. En C23, también se admite la sintaxis de atributos.

C++ agrega numerosas palabras clave adicionales para respaldar sus nuevas características. Esto hace que el código C que utiliza esas palabras clave como identificadores no sea válido en C++.

Por ejemplo:struct template { int new; struct template* class;

es un código C válido, pero es rechazado por un compilador de C++, ya que las palabras clave template, new y class están reservadas.

Construcciones que se comportan de manera diferente en C y C++ Hay algunas construcciones sintácticas que son válidas tanto en C como en C++ pero producen resultados diferentes en los dos lenguajes.

Los literales de carácter como 'a' son de tipo int en C y de tipo char en C++, lo que significa que sizeof 'a' generalmente dará resultados diferentes en los dos lenguajes: en C++, será 1, mientras que en C será sizeof(int) . Como 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.