Compatibilidad de C y C++

Comparación de lenguajes de programación

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 adiciones 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 otro lado, 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]

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

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]

  • Una diferencia que se encuentra con frecuencia es que C tiene un tipado más débil en lo que respecta a los punteros. En concreto, C permite 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 mallocla 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 );            
  • C++ tiene reglas más complicadas sobre las asignaciones de punteros que agregan calificadores, ya que permite la asignación de 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).
  • C++ cambia algunas funciones de la biblioteca estándar de C para agregar funciones sobrecargadas adicionales con const calificadores de tipo , por ejemplo, strchrdevuelve 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]
  • C++ también es más estricto en las conversiones a enumeraciones: los ints no se pueden convertir implícitamente a enumeraciones como en C. Además, las constantes de enumeración ( enumenumeradores) siempre son de tipo inten C, mientras que son tipos distintos en C++ y pueden tener un tamaño diferente al de int. [ necesita actualización ]
  • En C++ constse debe inicializar una variable; en C esto no es necesario.
  • Los compiladores de C++ prohíben que goto o switch crucen una inicialización, como en el siguiente código C99:
    void fn ( void ) { ir a flack ; int i = 1 ; flack : ; }        
  • Si bien es sintácticamente válido, a da como resultado un comportamiento indefinido en C++ si los marcos de pilalongjmp() 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()
  • C permite múltiples definiciones tentativas de una única variable global en una única unidad de traducción , lo cual no es válido como violación de ODR en C++.
    int N ; int N = 10 ;    
  • En C, es válido declarar un nuevo tipo con el mismo nombre que un tipo existente struct, uniono enum, pero no es válido en C++, porque en C, structlos tipos union, y enumdeben 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 ;     
  • Las declaraciones de funciones que no son prototipos (estilo "K&R") no son válidas en C++; siguen siendo válidas en C hasta C23, [14] [15] aunque se las ha considerado obsoletas desde la estandarización original de C en 1990. (El término "obsoleto" es un término definido en el estándar ISO C, que significa una característica que "puede considerarse para su retirada en futuras revisiones" del estándar). De manera similar, las declaraciones de funciones implícitas (que utilizan funciones que no han sido declaradas) no están permitidas en C++ y no son válidas en C desde 1999.
  • En C hasta C23, [16] una declaración de función sin parámetros, p. ej 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).
  • Tanto en C como en C++, se pueden definir structtipos anidados, pero el alcance se interpreta de manera diferente: en C++, un tipo anidado structse 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.
  • C permite que los tipos struct, union, y enumse 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 .

  • La aritmética compleja que utiliza los tipos de datos primitivos float complexy double complexse agregó en el estándar C99_Complex , a través de la palabra clave y complexla 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]
  • Matrices de longitud variable. Esta característica puede provocar que el operador sizeof no requiera mucho tiempo de compilación . [18]
    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 }                      
  • El último miembro de un tipo de estructura C99 con más de un miembro puede ser un miembro de matriz flexible , que adopta la forma sintáctica de una matriz con una longitud no especificada. Esto tiene un propósito similar al de las matrices de longitud variable, pero los VLA no pueden aparecer en las definiciones de tipo y, a diferencia de los VLA, los miembros de matriz flexible no tienen un tamaño definido. ISO C++ no tiene esa característica. Ejemplo:
    estructura X { int n , m ; char bytes []; }      
  • El 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.
  • Los calificadores de parámetros de matriz en funciones son compatibles con C pero no con C++.
    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       
  • La funcionalidad de los literales compuestos en C se generaliza tanto a los tipos integrados 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 }; // 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) {}              
  • Los inicializadores designados para matrices solo son válidos en C:
    char s [ 20 ] = { [ 0 ] = 'a' , [ 8 ] = 'g' }; // permitido en C, no en C++           
  • En C++, las funciones que no retornan pueden anotarse mediante un 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 ; };       
es un código C válido, pero es rechazado por un compilador de C++, ya que las palabras claves templatey están reservadas new.class

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 caracteres como 'a'son de tipo inten C y de tipo charen 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 chares 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 las variables con ámbito de espacio de nombres consta menos que se declaren explícitamente extern, a diferencia de C, en el que externes 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.
  • En C, el uso de funciones en línea 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 asegurar que se vincule una versión no en línea, mientras que C++ maneja esto automáticamente. Con más detalle, C distingue dos tipos de definiciones de inlinefunciones : definiciones externas ordinarias (donde externse 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 inlineen 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++.
  • Tanto C99 como C++ tienen un tipo booleano bool con constantes truey false, pero se definen de forma diferente. En C++, booles un tipo integrado y una palabra clave reservada . En C99, _Boolse introduce una nueva palabra clave, , como el nuevo tipo booleano. El encabezado stdbool.hproporciona macros bool, truey falseque se definen como _Bool, 1y 0, respectivamente. Por lo tanto, truey falsetienen tipo inten C. Sin embargo, es probable que esto cambie en C23 , cuyo borrador incluye cambiar bool, true, y falsepara que se conviertan en palabras clave, y dar truey falseel tipo bool.
  • En C, la implementación define si un campo de bits de tipo inttiene 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 structdelante 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 externdeclaració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 typedefsurta 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 sizeofoperador. El uso sizeof Tesperaría Tser una expresión y no un tipo, y por lo tanto, el ejemplo no se compilaría con C++.

Vinculación de código C y 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:

  • Los compiladores de C no nombran los símbolos mangle de la misma manera que lo hacen los compiladores de C++. [21]
  • Dependiendo del compilador y la arquitectura, también puede darse el caso de que las convenciones de llamada difieran entre los dos lenguajes.

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.

Referencias

  1. ^ ab Stroustrup, Bjarne . "An Overview of the C++ Programming Language in The Handbook of Object Technology (Editor: Saba Zamir). CRC Press LLC, Boca Raton. 1999. ISBN 0-8493-3135-8" (PDF) . p. 4. Archivado (PDF) desde el original el 16 de agosto de 2012 . Consultado el 12 de agosto de 2009 .
  2. ^ B.Stroustrup. "C y C++: hermanos. The C/C++ Users Journal. Julio de 2002" (PDF) . Consultado el 17 de marzo de 2019 .
  3. ^ "Preguntas frecuentes de Bjarne Stroustrup: ¿C es un subconjunto de C++?" . Consultado el 22 de septiembre de 2019 .
  4. ^ B. Stroustrup. "C y C++: un caso de compatibilidad. The C/C++ Users Journal. Agosto de 2002" (PDF) . Archivado (PDF) desde el original el 22 de julio de 2012. Consultado el 18 de agosto de 2013 .
  5. ^ Fundamento del Estándar Internacional —Lenguajes de Programación—C Archivado el 6 de junio de 2016 en Wayback Machine , revisión 5.10 (abril de 2003).
  6. ^ "Opciones del dialecto C: uso de la colección de compiladores GNU (GCC)". gnu.org . Archivado desde el original el 26 de marzo de 2014.
  7. ^ "N4659: Borrador de trabajo, Norma para el lenguaje de programación C++" (PDF) . §Anexo C.1. Archivado (PDF) desde el original el 7 de diciembre de 2017.("No es válido omitir una declaración con inicializador explícito o implícito (excepto en todo el bloque no ingresado). … Con esta simple regla de tiempo de compilación, C++ asegura que si una variable inicializada está dentro del alcance, entonces con certeza ha sido inicializada").
  8. ^ "N4659: Borrador de trabajo, Norma para el lenguaje de programación C++" (PDF) . §Anexo C.1. Archivado (PDF) desde el original el 7 de diciembre de 2017.
  9. ^ "Centro de conocimiento de IBM". ibm.com .
  10. ^ "Preguntas frecuentes > Casting malloc - Cprogramming.com". faq.cprogramming.com . Archivado desde el original el 5 de abril de 2007.
  11. ^ "4.4a — Conversión explícita de tipos (casting)". 16 de abril de 2015. Archivado desde el original el 25 de septiembre de 2016.
  12. ^ "Funciones de la biblioteca estándar que preservan calificadores, v4" (PDF) .
  13. ^ "longjmp - Referencia de C++". www.cplusplus.com . Archivado desde el original el 19 de mayo de 2018.
  14. ^ "WG14 N2432: Eliminar soporte para definiciones de funciones con listas de identificadores" (PDF) .
  15. ^ "Borrador de norma ISO C 2011" (PDF) .
  16. ^ "WG14 N 2841: No hay declaradores de funciones sin prototipos".
  17. ^ "std::complex - cppreference.com". es.cppreference.com . Archivado desde el original el 15 de julio de 2017.
  18. ^ "Incompatibilidades entre ISO C e ISO C++". Archivado desde el original el 9 de abril de 2006.
  19. ^ Punteros restringidos Archivado el 6 de agosto de 2016 en Wayback Machine desde Uso de la colección de compiladores GNU (GCC)
  20. ^ "WG14-N2764: El atributo noreturn" (PDF) . open-std.org . 21 de junio de 2021. Archivado (PDF) del original el 25 de diciembre de 2022.
  21. ^ "Centro de conocimiento de IBM". ibm.com .
  22. ^ "Centro de conocimiento de IBM". ibm.com .
  23. ^ "Documentación de Oracle". Docs.sun.com. Archivado desde el original el 3 de abril de 2009. Consultado el 18 de agosto de 2013 .
  • Comparación detallada, oración por oración, desde una perspectiva estándar C89.
  • Incompatibilidades entre ISO C e ISO C++, David R. Tribble (agosto de 2001).
  • Guía de migración de C++ a Oracle (Sun Microsystems), sección 3.11, documentación del compilador Oracle/Sun sobre el ámbito de vinculación.
  • Oracle: mezcla de código C y C++ en el mismo programa, descripción general por Steve Clamage (presidente del Comité C++ de ANSI).

Retrieved from "https://en.wikipedia.org/w/index.php?title=Compatibility_of_C_and_C%2B%2B&oldid=1252272474"