编译器如何避免模板的每个新类型实例化导致编译二进制文件大小呈线性增长?
我不知道在使用新的实例化时,我们怎么能避免复制所有模板代码。
我觉得在一个相对较大的代码库中,除了最简单的模板之外,编译时间和二进制文件大小会变得非常难以处理。但是它们的普及表明编译器可以通过一些魔法使它们实用。
编译器如何避免模板的每个新类型实例化导致编译二进制文件大小呈线性增长?
我不知道在使用新的实例化时,我们怎么能避免复制所有模板代码。
我觉得在一个相对较大的代码库中,除了最简单的模板之外,编译时间和二进制文件大小会变得非常难以处理。但是它们的普及表明编译器可以通过一些魔法使它们实用。
extern
,这允许定义在专门已知被实例化的特化上显式可见而不会被隐式实例化。我认为你对模板的实现方式有所误解。模板是根据需要编译成对应的类/函数。
考虑以下代码...
template <typename Type>
Type mymax(Type a, Type b) {
return a > b ? a : b;
}
int main(int argc, char** argv)
{
}
.file "example.cpp"
.text
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1"
.section .note.GNU-stack,"",@progbits
你会注意到它只包含主函数。现在我更新我的代码以使用模板函数。
int main(int argc, char** argv)
{
mymax<double>(3,4);
}
编译后,我得到了一个更长的汇编输出,包括用于处理双精度浮点数的模板函数。编译器看到该模板函数被类型“double”使用,因此创建了一个特定的函数来处理这种情况。
.file "example.cpp"
.text
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movabsq $4616189618054758400, %rdx
movabsq $4613937818241073152, %rax
movq %rdx, -24(%rbp)
movsd -24(%rbp), %xmm1
movq %rax, -24(%rbp)
movsd -24(%rbp), %xmm0
call _Z5mymaxIdET_S0_S0_
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.section .text._Z5mymaxIdET_S0_S0_,"axG",@progbits,_Z5mymaxIdET_S0_S0_,comdat
.weak _Z5mymaxIdET_S0_S0_
.type _Z5mymaxIdET_S0_S0_, @function
_Z5mymaxIdET_S0_S0_:
.LFB2:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movsd %xmm0, -8(%rbp)
movsd %xmm1, -16(%rbp)
movsd -8(%rbp), %xmm0
ucomisd -16(%rbp), %xmm0
jbe .L9
movq -8(%rbp), %rax
jmp .L6
.L9:
movq -16(%rbp), %rax
.L6:
movq %rax, -24(%rbp)
movsd -24(%rbp), %xmm0
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE2:
.size _Z5mymaxIdET_S0_S0_, .-_Z5mymaxIdET_S0_S0_
.ident "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1"
.section .note.GNU-stack,"",@progbits
int main(int argc, char** argv)
{
mymax<double>(3,4);
mymax<double>(4,5);
}
让我们再来看看它创建的汇编代码。与先前输出相比,它是可比的,因为大部分代码只是编译器将函数mymax创建其中,而“Type”被更改为双精度浮点数。无论我使用该函数多少次,它都只会被声明一次。
.file "example.cpp"
.text
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movabsq $4616189618054758400, %rdx
movabsq $4613937818241073152, %rax
movq %rdx, -24(%rbp)
movsd -24(%rbp), %xmm1
movq %rax, -24(%rbp)
movsd -24(%rbp), %xmm0
call _Z5mymaxIdET_S0_S0_
movabsq $4617315517961601024, %rdx
movabsq $4616189618054758400, %rax
movq %rdx, -24(%rbp)
movsd -24(%rbp), %xmm1
movq %rax, -24(%rbp)
movsd -24(%rbp), %xmm0
call _Z5mymaxIdET_S0_S0_
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.section .text._Z5mymaxIdET_S0_S0_,"axG",@progbits,_Z5mymaxIdET_S0_S0_,comdat
.weak _Z5mymaxIdET_S0_S0_
.type _Z5mymaxIdET_S0_S0_, @function
_Z5mymaxIdET_S0_S0_:
.LFB2:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movsd %xmm0, -8(%rbp)
movsd %xmm1, -16(%rbp)
movsd -8(%rbp), %xmm0
ucomisd -16(%rbp), %xmm0
jbe .L9
movq -8(%rbp), %rax
jmp .L6
.L9:
movq -16(%rbp), %rax
.L6:
movq %rax, -24(%rbp)
movsd -24(%rbp), %xmm0
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE2:
.size _Z5mymaxIdET_S0_S0_, .-_Z5mymaxIdET_S0_S0_
.ident "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1"
.section .note.GNU-stack,"",@progbits
基本上,模板不会增加代码的执行大小,就像手写函数一样。它只是一种方便的方法。编译器将为给定类型的一个或多个用途创建一个函数,因此,如果我使用它1次或1000次,只会有一个实例。现在,如果我更新我的代码以处理新类型(例如浮点数),我将在可执行文件中得到另一个函数,但无论我使用该函数多少次,只会有一个。