#ifdef __cplusplus
extern "C" {
#endif
extern int test(int, int);
#ifdef __cplusplus
}
#endif
在这段代码中,#ifdef __cplusplus 和 #endif 之间的代码是为了在 C++ 中使用 C 语言的函数声明和定义时,确保编译器正确地处理 C 和 C++ 之间的语法差异。这是因为 C 和 C++ 有一些不同之处,包括函数名的重载、类型转换等。
在这段代码中,extern “C” 是一个 C++ 的特性,它告诉编译器要按照 C 的方式来处理其中的函数。具体来说:
#ifdef __cplusplus:这个条件编译指令检查是否正在编译 C++ 代码。__cplusplus 是一个宏,当编译器编译 C++ 代码时,它会被定义。因此,如果代码正在编译为 C++,那么这个条件成立。
extern “C” {:如果正在编译为 C++,那么 extern “C” 会告诉编译器,接下来的代码块中的函数应该以 C 的方式进行链接,而不是 C++ 的方式。这是因为在 C++ 中,函数名可以发生函数重载(同一个函数名可以有不同的参数列表),而在 C 中没有这个概念。因此,通过将函数声明包裹在 extern “C” 中,可以确保它们以 C 的方式进行链接,这样cpp代码可以与纯 C 代码一起使用而不会出现问题。
#endif:这是条件编译的结束指令,它用来结束 #ifdef __cplusplus 开始的条件编译块。如果不是在编译 C++ 代码,这个块中的代码会被忽略。
总之,这段代码的作用是确保在 C++ 中使用 C 语言函数时,编译器不会将其与 C++ 的语法特性混淆,从而确保代码的正确性。这在涉及到与 C 代码互操作的情况下非常有用,因为 C++ 和 C 之间存在一些重要的差异。
C++ 在编译时会对函数的名字进行改编,这个过程被称为名称修饰(Name Mangling)。这是因为 C++ 允许函数重载,也就是说,可以定义多个同名函数,只要它们的参数列表不同。为了区分这些重载函数,C++ 编译器会对函数名进行修饰,将参数信息包含在函数名中,从而创建唯一的函数标识符。
举例来说,如果有以下两个函数:
int add(int a, int b);
double add(double a, double b);
在编译时,C++ 编译器会对这两个函数的名称进行改编,以便区分它们。名称修饰的结果可能会变成类似于 _Z3addii 和 _Z3adddd 这样的标识符,具体的修饰规则取决于编译器和平台。
然而,C 语言不支持函数重载,因此在 C 中不需要进行名称修饰。函数的名称在 C 中保持原样。这就是为什么在 C++ 中与 C 代码进行交互时需要使用 extern “C” 声明来告诉编译器不要对函数名称进行修饰,以便正确链接到 C 代码。
所以,为了确保 C++ 代码与 C 代码正确地进行交互,需要使用 extern “C” 声明,以防止 C++ 编译器对函数名称进行修饰,从而保持与 C 代码的兼容性。这样可以确保 C++ 代码能够与不进行名称修饰的 C 函数正确匹配。
名称修饰(Name Mangling)是一个高级语言(如C++)到汇编语言层面的概念,它涉及到函数和类的名称在汇编代码中的表示方式。不同编译器和平台可能会有不同的名称修饰规则,下面我将通过一个简单的例子来说明这个概念。
首先,让我们看一个简单的C++代码(extern “C”):
#include
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;
}
接下来,我们将使用 g++ 编译这段代码,并通过汇编工具查看生成的汇编代码。可以使用以下命令来编译代码并生成汇编文件:
g++ -S -o example.s example.cpp
上述命令中,-S 选项告诉 g++ 生成汇编代码,并将其输出到 example.s 文件中。现在,让我们查看生成的 example.s 文件:
.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:
在上述汇编代码中,我们关注的是 add函数和 main 函数。注意以下几点:
.globl add表示 add 函数是全局可见的,这是因为我们在 main 函数中调用了它。
.type add, @function 表示 add 函数是一个函数。
.size add, .-add 给出了 add 函数的大小。
add: 标识了 add 函数的入口点。
call add是在 main 函数中调用 add 函数的指令。
从这个例子中,可以看到函数 add 在汇编代码中的名称没有发生明显的变化,因为这是一个普通的 C 函数。名称修饰通常在涉及到函数重载、命名空间、类等高级语言特性时变得更加复杂。不同的编译器和平台可能会使用不同的规则来进行名称修饰,所以具体的名称修饰方式会有所不同。
下面是不使用extern "C"的汇编代码:
#include
int add(int a, int b) {
return a + b;
}
int main() {
int result = add(3, 4);
std::cout << result << std::endl;
return 0;
}
再次使用以下命令来编译代码并生成汇编文件:
g++ -S -o example.s example.cpp
查看生成的 example.s 文件:
.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:
要查看真正的名称修饰效果,可以尝试创建一个包含类和函数重载的更复杂的 C++ 示例,并查看生成的汇编代码,以了解不同情况下的名称修饰规则。但需要注意的是,名称修饰通常是编译器的内部实现细节,不同编译器可能会有不同的名称修饰方案。
下面是一个包含类和函数重载的较复杂的 C++ 示例:
#include
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;
}
在这个示例中,我们定义了一个名为 Math 的类,其中包含了两个 add 函数,一个用于整数相加,另一个用于浮点数相加。这两个函数具有相同的名称,但参数类型不同,这就是函数重载。
接下来,我们编译这个示例并查看生成的汇编代码。使用以下命令来生成汇编文件:
g++ -S -o complex_example.s complex_example.cpp
现在,让我们查看生成的 complex_example.s 文件中与 Math 类和 add 函数相关的部分:
.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:
在这个汇编代码中,可以看到两个 add 函数的名称发生了变化,它们被命名为 _ZN4Math3addEii 和 _ZN4Math3addEdd。这些名称修饰是由编译器根据函数的参数类型生成的,以确保在汇编级别可以区分这两个重载函数。
这个示例演示了名称修饰在涉及到函数重载的情况下的应用,以确保正确的函数被调用。在实际应用中,名称修饰对于支持函数重载、运算符重载以及类的成员函数等高级语言特性非常重要。