[c/c++] Compilación mixta de c y cpp

compilación mixta c y cpp

#ifdef __cplusplus
extern "C" {
    
    
#endif

extern int test(int, int);

#ifdef __cplusplus
}
#endif

En este código, el código entre #ifdef __cplusplus y #endif es para garantizar que el compilador maneje correctamente las diferencias de sintaxis entre C y C++ cuando se utilizan declaraciones y definiciones de funciones en lenguaje C en C++. Esto se debe a que existen algunas diferencias entre C y C++, incluida la sobrecarga de nombres de funciones, conversiones de tipos, etc.

En este código, "C" externo es una característica de C++ que le dice al compilador que trate las funciones en la forma C. Específicamente:

#ifdef __cplusplus: esta directiva de compilación condicional comprueba si se está compilando código C++. __cplusplus es una macro que se define cuando el compilador compila código C++. Por lo tanto, si el código se compila en C++, esta condición se cumple.

externa “C” { : si está compilando en C++, entonces el "C" externo le dice al compilador que las funciones en el siguiente bloque de código deben vincularse en C, no en C++. Esto se debe a que en C++, los nombres de funciones se pueden sobrecargar (el mismo nombre de función puede tener diferentes listas de parámetros), pero no existe tal concepto en C. Entonces, al envolver las declaraciones de funciones en "C" externo, se asegura de que estén vinculadas en forma C para que el código cpp se pueda usar con código C puro sin problemas.

#endif: esta es la instrucción final de la compilación condicional. Se utiliza para finalizar el bloque de compilación condicional iniciado por #ifdef __cplusplus. Si no está compilando código C++, el código de este bloque se ignora.

En resumen, el propósito de este código es garantizar que cuando se utilizan funciones del lenguaje C en C++, el compilador no las confunda con las características de sintaxis de C++, garantizando así la corrección del código. Esto es útil cuando se trata de interoperar con código C, ya que existen algunas diferencias importantes entre C++ y C.

Cuando se compila C++, se cambiará el nombre de la función.

C++ adaptará el nombre de la función durante la compilación. Este proceso se llama alteración de nombres. Esto se debe a que C++ permite la sobrecarga de funciones, es decir, se pueden definir múltiples funciones con el mismo nombre siempre que sus listas de parámetros sean diferentes. Para distinguir estas funciones sobrecargadas, el compilador de C++ modifica el nombre de la función para incluir información de parámetros en el nombre de la función, creando así un identificador de función único.

Por ejemplo, si tiene las siguientes dos funciones:

int add(int a, int b);
double add(double a, double b);

En tiempo de compilación, el compilador de C++ alteró los nombres de estas dos funciones para distinguirlas. El resultado de la manipulación de nombres puede convertirse en identificadores como _Z3addii y _Z3adddd, según el compilador y la plataforma.

Sin embargo, el lenguaje C no admite la sobrecarga de funciones, por lo que no es necesario modificar nombres en C. El nombre de la función permanece sin cambios en C. Es por eso que al interactuar con código C en C++ es necesario usar la declaración "C" externa para indicarle al compilador que no decore el nombre de la función para poder vincularse correctamente al código C.

Por lo tanto, para garantizar que el código C++ interactúe correctamente con el código C, se deben utilizar declaraciones "C" externas para evitar que el compilador de C++ adorne el nombre de la función, manteniendo así la compatibilidad con el código C. Esto garantiza que el código C++ coincida correctamente con las funciones C sin decoración de nombres.

Explicación detallada desde el punto de vista del montaje.

La manipulación de nombres es un concepto que va desde los lenguajes de alto nivel (como C ++) hasta el nivel del lenguaje ensamblador e implica la forma en que los nombres de funciones y clases se representan en el código ensamblador. Diferentes compiladores y plataformas pueden tener diferentes reglas de modificación de nombres. A continuación ilustraré este concepto con un ejemplo simple.

Primero, veamos un código C++ simple (externo "C"):

#include <iostream>
extern "C"
{
    
    
int add(int a, int b) {
    
    
    return a + b;
}
}
int main() {
    
    
    int result = add(3, 4);
    std::cout << result << std::endl;
    return 0;
}

A continuación, compilaremos este código usando g++ y veremos el código ensamblador resultante a través de una herramienta ensambladora. Puede utilizar los siguientes comandos para compilar el código y generar un archivo ensamblador:

g++ -S -o example.s example.cpp

En el comando anterior, la opción -S le dice a g++ que genere código ensamblador y lo envíe al archivo example.s. Ahora, echemos un vistazo al archivo example.s generado:

	.file	"example.cpp"
	.text
	.local	_ZStL8__ioinit
	.comm	_ZStL8__ioinit,1,1
	.globl	add
	.type	add, @function
add:
.LFB1731:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	addl	%edx, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1731:
	.size	add, .-add
	.globl	main
	.type	main, @function
main:
.LFB1732:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	$4, %esi
	movl	$3, %edi
	call	add
	movl	%eax, -4(%rbp)
	movl	-4(%rbp), %eax
	movl	%eax, %esi
	leaq	_ZSt4cout(%rip), %rax
	movq	%rax, %rdi
	call	_ZNSolsEi@PLT
	movq	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
	movq	%rdx, %rsi
	movq	%rax, %rdi
	call	_ZNSolsEPFRSoS_E@PLT
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1732:
	.size	main, .-main
	.type	_Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB2232:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	cmpl	$1, -4(%rbp)
	jne	.L7
	cmpl	$65535, -8(%rbp)
	jne	.L7
	leaq	_ZStL8__ioinit(%rip), %rax
	movq	%rax, %rdi
	call	_ZNSt8ios_base4InitC1Ev@PLT
	leaq	__dso_handle(%rip), %rax
	movq	%rax, %rdx
	leaq	_ZStL8__ioinit(%rip), %rax
	movq	%rax, %rsi
	movq	_ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
	movq	%rax, %rdi
	call	__cxa_atexit@PLT
.L7:
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE2232:
	.size	_Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
	.type	_GLOBAL__sub_I_add, @function
_GLOBAL__sub_I_add:
.LFB2233:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$65535, %esi
	movl	$1, %edi
	call	_Z41__static_initialization_and_destruction_0ii
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE2233:
	.size	_GLOBAL__sub_I_add, .-_GLOBAL__sub_I_add
	.section	.init_array,"aw"
	.align 8
	.quad	_GLOBAL__sub_I_add
	.hidden	__dso_handle
	.ident	"GCC: (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	1f - 0f
	.long	4f - 1f
	.long	5
0:
	.string	"GNU"
1:
	.align 8
	.long	0xc0000002
	.long	3f - 2f
2:
	.long	0x3
3:
	.align 8
4:

En el código ensamblador anterior, nos centramos en la función agregar y la función principal. Tenga en cuenta los siguientes puntos:

  1. .globl add significa que la función agregar es globalmente visible porque la llamamos en la función principal.

  2. .type add, @function indica que la función agregar es una función.

  3. .size add, .-add proporciona el tamaño de la función agregar.

  4. agregar: identifica el punto de entrada de la función agregar.

  5. call add es una instrucción para llamar a la función add en la función principal.

En este ejemplo, puede ver que el nombre de la función agregar no ha cambiado significativamente en el código ensamblador, porque es una función C normal. La decoración de nombres a menudo se vuelve más compleja cuando se trata de características del lenguaje de alto nivel, como sobrecarga de funciones, espacios de nombres, clases, etc. Diferentes compiladores y plataformas pueden usar diferentes reglas para la modificación de nombres, por lo que los métodos específicos de modificación de nombres serán diferentes.

Aquí está el código ensamblador sin "C" externa:

#include <iostream>

int add(int a, int b) {
    
    
    return a + b;
}

int main() {
    
    
    int result = add(3, 4);
    std::cout << result << std::endl;
    return 0;
}

Utilice el siguiente comando nuevamente para compilar el código y generar un archivo ensamblador:

g++ -S -o example.s example.cpp

Vea el archivo example.s generado:

	.file	"example.cpp"
   .text
   .local	_ZStL8__ioinit
   .comm	_ZStL8__ioinit,1,1
   .globl	_Z3addii
   .type	_Z3addii, @function
_Z3addii:
.LFB1731:
   .cfi_startproc
   endbr64
   pushq	%rbp
   .cfi_def_cfa_offset 16
   .cfi_offset 6, -16
   movq	%rsp, %rbp
   .cfi_def_cfa_register 6
   movl	%edi, -4(%rbp)
   movl	%esi, -8(%rbp)
   movl	-4(%rbp), %edx
   movl	-8(%rbp), %eax
   addl	%edx, %eax
   popq	%rbp
   .cfi_def_cfa 7, 8
   ret
   .cfi_endproc
.LFE1731:
   .size	_Z3addii, .-_Z3addii
   .globl	main
   .type	main, @function
main:
.LFB1732:
   .cfi_startproc
   endbr64
   pushq	%rbp
   .cfi_def_cfa_offset 16
   .cfi_offset 6, -16
   movq	%rsp, %rbp
   .cfi_def_cfa_register 6
   subq	$16, %rsp
   movl	$4, %esi
   movl	$3, %edi
   call	_Z3addii
   movl	%eax, -4(%rbp)
   movl	-4(%rbp), %eax
   movl	%eax, %esi
   leaq	_ZSt4cout(%rip), %rax
   movq	%rax, %rdi
   call	_ZNSolsEi@PLT
   movq	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
   movq	%rdx, %rsi
   movq	%rax, %rdi
   call	_ZNSolsEPFRSoS_E@PLT
   movl	$0, %eax
   leave
   .cfi_def_cfa 7, 8
   ret
   .cfi_endproc
.LFE1732:
   .size	main, .-main
   .type	_Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB2232:
   .cfi_startproc
   endbr64
   pushq	%rbp
   .cfi_def_cfa_offset 16
   .cfi_offset 6, -16
   movq	%rsp, %rbp
   .cfi_def_cfa_register 6
   subq	$16, %rsp
   movl	%edi, -4(%rbp)
   movl	%esi, -8(%rbp)
   cmpl	$1, -4(%rbp)
   jne	.L7
   cmpl	$65535, -8(%rbp)
   jne	.L7
   leaq	_ZStL8__ioinit(%rip), %rax
   movq	%rax, %rdi
   call	_ZNSt8ios_base4InitC1Ev@PLT
   leaq	__dso_handle(%rip), %rax
   movq	%rax, %rdx
   leaq	_ZStL8__ioinit(%rip), %rax
   movq	%rax, %rsi
   movq	_ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
   movq	%rax, %rdi
   call	__cxa_atexit@PLT
.L7:
   nop
   leave
   .cfi_def_cfa 7, 8
   ret
   .cfi_endproc
.LFE2232:
   .size	_Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
   .type	_GLOBAL__sub_I__Z3addii, @function
_GLOBAL__sub_I__Z3addii:
.LFB2233:
   .cfi_startproc
   endbr64
   pushq	%rbp
   .cfi_def_cfa_offset 16
   .cfi_offset 6, -16
   movq	%rsp, %rbp
   .cfi_def_cfa_register 6
   movl	$65535, %esi
   movl	$1, %edi
   call	_Z41__static_initialization_and_destruction_0ii
   popq	%rbp
   .cfi_def_cfa 7, 8
   ret
   .cfi_endproc
.LFE2233:
   .size	_GLOBAL__sub_I__Z3addii, .-_GLOBAL__sub_I__Z3addii
   .section	.init_array,"aw"
   .align 8
   .quad	_GLOBAL__sub_I__Z3addii
   .hidden	__dso_handle
   .ident	"GCC: (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0"
   .section	.note.GNU-stack,"",@progbits
   .section	.note.gnu.property,"a"
   .align 8
   .long	1f - 0f
   .long	4f - 1f
   .long	5
0:
   .string	"GNU"
1:
   .align 8
   .long	0xc0000002
   .long	3f - 2f
2:
   .long	0x3
3:
   .align 8
4:

Para ver el verdadero efecto de la alteración de nombres, intente crear un ejemplo de C++ más complejo que incluya sobrecarga de clases y funciones, y observe el código ensamblador generado para comprender las reglas de alteración de nombres en diferentes situaciones. Sin embargo, cabe señalar que la modificación de nombres suele ser un detalle de implementación interna del compilador, y diferentes compiladores pueden tener diferentes esquemas de modificación de nombres.

Aquí hay un ejemplo de C++ más complejo que involucra sobrecarga de clases y funciones:

#include <iostream>

class Math {
    
    
public:
   int add(int a, int b) {
    
    
       return a + b;
   }

   double add(double a, double b) {
    
    
       return a + b;
   }
};

int main() {
    
    
   Math math;
   int intResult = math.add(3, 4);
   double doubleResult = math.add(2.5, 3.7);

   std::cout << "Integer result: " << intResult << std::endl;
   std::cout << "Double result: " << doubleResult << std::endl;

   return 0;
}

En este ejemplo, definimos una clase llamada Math que contiene dos funciones de suma, una para sumar números enteros y otra para sumar números de punto flotante. Estas dos funciones tienen el mismo nombre pero diferentes tipos de parámetros, esto es sobrecarga de funciones.

A continuación, compilamos este ejemplo y vemos el código ensamblador generado. Utilice el siguiente comando para generar el archivo de ensamblaje:

g++ -S -o complex_example.s complex_example.cpp

Ahora, veamos las partes del archivo complex_example.s generado relacionadas con la clase Math y la función agregar:

   .file	"complex_example.cpp"
   .text
   .local	_ZStL8__ioinit
   .comm	_ZStL8__ioinit,1,1
   .section	.text._ZN4Math3addEii,"axG",@progbits,_ZN4Math3addEii,comdat
   .align 2
   .weak	_ZN4Math3addEii
   .type	_ZN4Math3addEii, @function
_ZN4Math3addEii:
.LFB1731:
   .cfi_startproc
   endbr64
   pushq	%rbp
   .cfi_def_cfa_offset 16
   .cfi_offset 6, -16
   movq	%rsp, %rbp
   .cfi_def_cfa_register 6
   movq	%rdi, -8(%rbp)
   movl	%esi, -12(%rbp)
   movl	%edx, -16(%rbp)
   movl	-12(%rbp), %edx
   movl	-16(%rbp), %eax
   addl	%edx, %eax
   popq	%rbp
   .cfi_def_cfa 7, 8
   ret
   .cfi_endproc
.LFE1731:
   .size	_ZN4Math3addEii, .-_ZN4Math3addEii
   .section	.text._ZN4Math3addEdd,"axG",@progbits,_ZN4Math3addEdd,comdat
   .align 2
   .weak	_ZN4Math3addEdd
   .type	_ZN4Math3addEdd, @function
_ZN4Math3addEdd:
.LFB1732:
   .cfi_startproc
   endbr64
   pushq	%rbp
   .cfi_def_cfa_offset 16
   .cfi_offset 6, -16
   movq	%rsp, %rbp
   .cfi_def_cfa_register 6
   movq	%rdi, -8(%rbp)
   movsd	%xmm0, -16(%rbp)
   movsd	%xmm1, -24(%rbp)
   movsd	-16(%rbp), %xmm0
   addsd	-24(%rbp), %xmm0
   movq	%xmm0, %rax
   movq	%rax, %xmm0
   popq	%rbp
   .cfi_def_cfa 7, 8
   ret
   .cfi_endproc
.LFE1732:
   .size	_ZN4Math3addEdd, .-_ZN4Math3addEdd
   .section	.rodata
.LC2:
   .string	"Integer result: "
.LC3:
   .string	"Double result: "
   .text
   .globl	main
   .type	main, @function
main:
.LFB1733:
   .cfi_startproc
   endbr64
   pushq	%rbp
   .cfi_def_cfa_offset 16
   .cfi_offset 6, -16
   movq	%rsp, %rbp
   .cfi_def_cfa_register 6
   subq	$32, %rsp
   movq	%fs:40, %rax
   movq	%rax, -8(%rbp)
   xorl	%eax, %eax
   leaq	-21(%rbp), %rax
   movl	$4, %edx
   movl	$3, %esi
   movq	%rax, %rdi
   call	_ZN4Math3addEii
   movl	%eax, -20(%rbp)
   movsd	.LC0(%rip), %xmm0
   movq	.LC1(%rip), %rdx
   leaq	-21(%rbp), %rax
   movapd	%xmm0, %xmm1
   movq	%rdx, %xmm0
   movq	%rax, %rdi
   call	_ZN4Math3addEdd
   movq	%xmm0, %rax
   movq	%rax, -16(%rbp)
   leaq	.LC2(%rip), %rax
   movq	%rax, %rsi
   leaq	_ZSt4cout(%rip), %rax
   movq	%rax, %rdi
   call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
   movq	%rax, %rdx
   movl	-20(%rbp), %eax
   movl	%eax, %esi
   movq	%rdx, %rdi
   call	_ZNSolsEi@PLT
   movq	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
   movq	%rdx, %rsi
   movq	%rax, %rdi
   call	_ZNSolsEPFRSoS_E@PLT
   leaq	.LC3(%rip), %rax
   movq	%rax, %rsi
   leaq	_ZSt4cout(%rip), %rax
   movq	%rax, %rdi
   call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
   movq	%rax, %rdx
   movq	-16(%rbp), %rax
   movq	%rax, %xmm0
   movq	%rdx, %rdi
   call	_ZNSolsEd@PLT
   movq	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
   movq	%rdx, %rsi
   movq	%rax, %rdi
   call	_ZNSolsEPFRSoS_E@PLT
   movl	$0, %eax
   movq	-8(%rbp), %rdx
   subq	%fs:40, %rdx
   je	.L7
   call	__stack_chk_fail@PLT
.L7:
   leave
   .cfi_def_cfa 7, 8
   ret
   .cfi_endproc
.LFE1733:
   .size	main, .-main
   .type	_Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB2237:
   .cfi_startproc
   endbr64
   pushq	%rbp
   .cfi_def_cfa_offset 16
   .cfi_offset 6, -16
   movq	%rsp, %rbp
   .cfi_def_cfa_register 6
   subq	$16, %rsp
   movl	%edi, -4(%rbp)
   movl	%esi, -8(%rbp)
   cmpl	$1, -4(%rbp)
   jne	.L10
   cmpl	$65535, -8(%rbp)
   jne	.L10
   leaq	_ZStL8__ioinit(%rip), %rax
   movq	%rax, %rdi
   call	_ZNSt8ios_base4InitC1Ev@PLT
   leaq	__dso_handle(%rip), %rax
   movq	%rax, %rdx
   leaq	_ZStL8__ioinit(%rip), %rax
   movq	%rax, %rsi
   movq	_ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
   movq	%rax, %rdi
   call	__cxa_atexit@PLT
.L10:
   nop
   leave
   .cfi_def_cfa 7, 8
   ret
   .cfi_endproc
.LFE2237:
   .size	_Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
   .type	_GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB2238:
   .cfi_startproc
   endbr64
   pushq	%rbp
   .cfi_def_cfa_offset 16
   .cfi_offset 6, -16
   movq	%rsp, %rbp
   .cfi_def_cfa_register 6
   movl	$65535, %esi
   movl	$1, %edi
   call	_Z41__static_initialization_and_destruction_0ii
   popq	%rbp
   .cfi_def_cfa 7, 8
   ret
   .cfi_endproc
.LFE2238:
   .size	_GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
   .section	.init_array,"aw"
   .align 8
   .quad	_GLOBAL__sub_I_main
   .section	.rodata
   .align 8
.LC0:
   .long	-1717986918
   .long	1074633113
   .align 8
.LC1:
   .long	0
   .long	1074003968
   .hidden	__dso_handle
   .ident	"GCC: (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0"
   .section	.note.GNU-stack,"",@progbits
   .section	.note.gnu.property,"a"
   .align 8
   .long	1f - 0f
   .long	4f - 1f
   .long	5
0:
   .string	"GNU"
1:
   .align 8
   .long	0xc0000002
   .long	3f - 2f
2:
   .long	0x3
3:
   .align 8
4:

En este código ensamblador, puede ver que los nombres de las dos funciones de adición han cambiado y se denominan _ZN4Math3addEii y _ZN4Math3addEdd. El compilador genera estas decoraciones de nombres en función de los tipos de parámetros de la función para garantizar que las dos funciones sobrecargadas se puedan distinguir en el nivel de ensamblado.

Este ejemplo demuestra el uso de la decoración de nombres en situaciones que involucran sobrecarga de funciones para garantizar que se llame a la función correcta. En aplicaciones prácticas, la modificación de nombres es muy importante para admitir características del lenguaje de alto nivel, como la sobrecarga de funciones, la sobrecarga de operadores y las funciones miembro de clases.

Supongo que te gusta

Origin blog.csdn.net/qq_43577613/article/details/132629288
Recomendado
Clasificación