# Compatibilidad entre C y C++

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) 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 espresiones goto y switch cruzen una inicialización, como en el siguiente bloque de código C:
* 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++.
* 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:
* 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, 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++.
* La funcionalidad de los literales compuestos en C se generaliza tanto a los tipos incoporados 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.
* Los inicializadores designados, en arrays, solo son válidos en 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:
    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 caracter 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.
* C++ asigna enlaces internos a variables const con alcance de espacio de nombres 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 alcance de archivo. En la práctica, esto no produce cambios semánticos entre código C y C++ idéntico, sino que provocará un error de vinculación o de tiempo de compilación.
* En C, el uso de funciones en línea (inline) requiere agregar manualmente una declaración de prototipo de la función usando la palabra clave extern en exactamente una unidad de traducción para garantizar que se vincule una versión no en línea, mientras que C++ maneja esto automáticamente. C distingue dos tipos de definiciones de funciones inline : definiciones externas ordinarias (donde extern se usa explícitamente) y definiciones en línea. C++, por otro lado, sólo proporciona 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 cantidad 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 de la vinculación de la función, pero no independiente. Los compiladores de C tienen la discreción de elegir entre utilizar definiciones en línea y externas de la misma función cuando ambas están visibles. Sin embargo, C++ requiere que si una función con enlace externo se declara inline en cualquier unidad de traducción, entonces debe declararse así (y, por lo tanto, también definirse) en cada unidad de traducción donde se use, y que todas las definiciones de esa función sean idénticas, siguiendo el principio ODR. Las funciones estáticas en línea se comportan de manera idéntica en C y C++.
* Tanto C99 como C++ tienen un tipo booleano bool con constantes true y false, pero se definen de manera diferente. En C++, bool es un tipo incorporado y una palabra clave reservada . En C99, se introduce una nueva palabra clave, _Bool, como el nuevo tipo booleano. El encabezado stdbool.h proporciona las 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.
* En C, la implementación es libre de definir si un campo de bits de tipo 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 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++:
Esto se debe a que C requiere la palabra reservada struct delante de las etiquetas de estructura (y por lo tanto sizeof(T) se refiere a la variable), pero C++ permite omitirlo (y por lo tanto sizeof(T) se refiere a la estructura, debido al typedef implícito). Tenga en cuenta que el resultado es diferente cuando la declaración extern se coloca dentro de la función: entonces, la presencia de un identificador con el mismo nombre en el ámbito de la función impide que el typedef implícito tenga 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 operador sizeof . El uso de sizeof T esperaría que T fuera una expresión y no un tipo y, por lo tanto, el ejemplo no se compilaría con C++.

## Enlace de código C y C++

Si bien C y C++ mantienen un alto grado de compatibilidad a nivel de código, los archivos de objetos que producen sus respectivos compiladores pueden tener diferencias importantes que se manifiestan al mezclar código C y C++.
Notablemente:
* Los compiladores de C no nombran los símbolos ("name mangling") de la misma manera que lo hacen los compiladores de C++.
* Dependiendo del compilador y la arquitectura, también puede darse el caso de que las convenciones de llamada difieran entre los dos lenguajes.
Por estas razones, para que 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:
Las diferencias entre las convenciones de llamada y vinculación 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 C++ que no está declarada extern "C" .
Por ejemplo, el siguiente código:
Al utilizar el compilador C++ de Sun Microsystems, produce la siguiente advertencia:
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 C foo .