为什么允许指向内联函数的指针?
Why are pointers to inline functions allowed?
我有两个问题:
1) 为什么在 C++ 中允许指向内联函数的指针?
我读到内联函数的代码只是被复制到函数调用语句中,内联函数中没有编译时内存分配。那么,既然内联函数没有固定的内存地址,为什么可以存在指向内联函数的指针呢?
2) 考虑下面的代码:
inline void func()
{
int n=0;
cout<<(&n);
}
每次调用func()
时,不应该打印n
地址的不同值吗?
[因为我认为每次复制内联函数代码时,都必须重新分配局部变量(而在普通函数的情况下,会发生重新初始化)]
我是初学者,为了加强概念才问这个问题。有不对的地方请指正
inline
keyword 最初是对编译器的提示,表明您作为程序员认为此函数是内联的候选函数 - 编译器不需要遵守这一点。
在现代用法中,它与内联几乎没有关系 - 现代编译器可以自由内联(或不内联)函数 "behind you back",这些构成了优化技术的一部分。
代码转换(包括内联)在C++"as-if" rule下完成,基本上就是编译器想怎么转换就怎么转换,只要由于执行是 "as-if" 原始代码按编写执行。此规则推动了 C++ 中的优化。
也就是说,一旦函数的地址被获取,它就必须存在(即地址必须有效)。这可能意味着它不再内联,但仍然可以(优化器将应用适当的分析)。
So why can a pointer exist to a inline function, given that there is no fixed memory address of inline functions?
不,这只是一个提示,主要与链接有关,而不是实际的内联。这助长了可以说是当前的主要用途,即在头文件中定义函数。
Should it not print different values of address of n
each time func()
is called?
可能,n
是一个局部变量,基于函数执行时的堆栈位置。也就是说,函数inline
,它涉及链接,链接器将合并翻译单元上的函数。
如 所述;
... that if the example is changed to static int n
, then every call to the function must print a constant value (in a single program run of course) ... and that is true whether or not the code is inlined or not.
这又是链接要求对局部变量的影响 n
。
内联函数并不总是内联的。它只是表示程序员希望内联此函数。 The compiler is allowed to inline any function, regarless of whether inline keyword was used or not.
如果使用函数的地址,函数很可能没有内联在最终的可执行文件中,至少在 GCC 中是这样:
When a function is both inline and static, if all calls to the function are integrated into the caller, and the function's address is never used, then the function's own assembler code is never referenced.
你读旧material。现在使用 inline
的主要原因是允许 header 文件中的函数体。将 inline
关键字与函数一起使用向链接器发出信号,表明可以组合跨翻译单元的所有函数实例;在多个单元包含的 header 中具有 non-inline 函数会由于违反单一定义规则而导致未定义的行为。
C++17还增加了inline variables,它和属性一样,变量可以定义在一个header中,所有的定义都是由链接器组合而不是导致 ODR 违规。
您与 "code getting copied to the calling function" 谈论的内容称为 inlining 并且独立于 inline
关键字。编译器将根据优化设置决定是否对 non-inline 函数和内联函数执行此操作。
1) Why pointers to inline functions are allowed in c++?
因为内联函数和其他函数一样,指向它们是您可以用函数做的事情之一。内联函数在这方面并不特别。
I have read that code of inline functions just get copied to the function calling statement and there is no compile time memory allocations in inline functions.
您(也许还有您读过的 material)混合了两个相关且命名相似的概念。
一个内联函数在所有使用它的翻译单元中定义,而一个non-inline函数只在一个翻译单元中根据一个定义规则的要求定义。这就是函数的内联声明的含义;它放宽了单一定义规则,但也提出了在所有使用它的翻译单元中定义的额外要求(如果 odr 没有放宽,这是不可能的)。
内联扩展(或内联)是一种优化,通过将被调用函数复制到调用者的框架中来避免函数调用。函数调用可以内联扩展,无论该函数是否已声明为内联。并且声明为内联的函数不一定是展开内联的。
但是,无法在未定义的翻译单元中内联扩展函数(除非 link 时间优化执行扩展)。因此,在内联声明允许的所有 TU 中定义的要求,也通过允许在调用它的所有 TU 中定义函数,使函数的内联扩展成为可能。但不能保证优化。
2) Should it not print different values of address of n each time func() is called?
内联扩展确实会导致局部变量位于调用者的框架中,是的。但如果调用来自不同的帧,则无论扩展如何,它们的位置都会不同。
通常会为任何已内联扩展的函数生成一个常规 non-expanded 版本。如果函数的地址被获取,它将指向那个 non-expanded 函数。如果编译器可以证明对函数的所有调用都是内联的,编译器可能会选择根本不提供 non-expanded 版本。这要求该函数具有内部 linkage,并且获取函数的地址通常会使此类证明变得非常困难或不可能。
除了已经说过的一点,inline
函数实际上不需要内联(许多没有 inline
的函数被现代编译器内联 ) , 通过函数指针 内联 调用也是完全可以想象的。示例:
#include <iostream>
int foo(int (*fun)(int), int x) {
return fun(x);
}
int succ(int n) {
return n+1;
}
int main() {
int c=0;
for (int i=0; i<10000; ++i) {
c += foo(succ, i);
}
std::cout << c << std::endl;
}
在这里,foo(succ, i)
可以 作为一个整体 内联到 i+1
。确实这似乎发生了†:g++ -O3 -S
为 foo
和 succ
函数生成代码
_Z3fooPFiiEi:
.LFB998:
.cfi_startproc
movq %rdi, %rax
movl %esi, %edi
jmp *%rax
.cfi_endproc
.LFE998:
.size _Z3fooPFiiEi, .-_Z3fooPFiiEi
.p2align 4,,15
.globl _Z4succi
.type _Z4succi, @function
_Z4succi:
.LFB999:
.cfi_startproc
leal 1(%rdi), %eax
ret
.cfi_endproc
但随后它为 main
生成代码, 从未引用 任何一个,而是只包含一个新的专用 _GLOBAL__sub_I__Z3fooPFiiEi
:
.LFE999:
.size _Z4succi, .-_Z4succi
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB1000:
.cfi_startproc
movdqa .LC1(%rip), %xmm4
xorl %eax, %eax
pxor %xmm1, %xmm1
movdqa .LC0(%rip), %xmm0
movdqa .LC2(%rip), %xmm3
jmp .L5
.p2align 4,,10
.p2align 3
.L8:
movdqa %xmm2, %xmm0
.L5:
movdqa %xmm0, %xmm2
addl , %eax
paddd %xmm3, %xmm0
cmpl 00, %eax
paddd %xmm0, %xmm1
paddd %xmm4, %xmm2
jne .L8
movdqa %xmm1, %xmm5
subq , %rsp
.cfi_def_cfa_offset 32
movl $_ZSt4cout, %edi
psrldq , %xmm5
paddd %xmm5, %xmm1
movdqa %xmm1, %xmm6
psrldq , %xmm6
paddd %xmm6, %xmm1
movdqa %xmm1, %xmm7
movd %xmm7, 12(%rsp)
movl 12(%rsp), %esi
call _ZNSolsEi
movq %rax, %rdi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
xorl %eax, %eax
addq , %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE1000:
.size main, .-main
.p2align 4,,15
.type _GLOBAL__sub_I__Z3fooPFiiEi, @function
_GLOBAL__sub_I__Z3fooPFiiEi:
.LFB1007:
.cfi_startproc
subq , %rsp
.cfi_def_cfa_offset 16
movl $_ZStL8__ioinit, %edi
call _ZNSt8ios_base4InitC1Ev
movl $__dso_handle, %edx
movl $_ZStL8__ioinit, %esi
movl $_ZNSt8ios_base4InitD1Ev, %edi
addq , %rsp
.cfi_def_cfa_offset 8
jmp __cxa_atexit
.cfi_endproc
.LFE1007:
.size _GLOBAL__sub_I__Z3fooPFiiEi, .-_GLOBAL__sub_I__Z3fooPFiiEi
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I__Z3fooPFiiEi
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
所以在这种情况下,实际程序甚至不包含指向 succ
的函数指针——编译器发现这个指针无论如何总是指向同一个函数,因此能够消除整个事情没有改变行为。当您经常通过函数指针调用小函数时,这可以大大提高性能。这是函数式语言中相当普遍的技术; O'Caml 和 Haskell 等语言的编译器充分利用了这种优化。
†免责声明:我的组装技能几乎不存在。我可能是在胡说八道。
我有两个问题:
1) 为什么在 C++ 中允许指向内联函数的指针? 我读到内联函数的代码只是被复制到函数调用语句中,内联函数中没有编译时内存分配。那么,既然内联函数没有固定的内存地址,为什么可以存在指向内联函数的指针呢?
2) 考虑下面的代码:
inline void func()
{
int n=0;
cout<<(&n);
}
每次调用func()
时,不应该打印n
地址的不同值吗?
[因为我认为每次复制内联函数代码时,都必须重新分配局部变量(而在普通函数的情况下,会发生重新初始化)]
我是初学者,为了加强概念才问这个问题。有不对的地方请指正
inline
keyword 最初是对编译器的提示,表明您作为程序员认为此函数是内联的候选函数 - 编译器不需要遵守这一点。
在现代用法中,它与内联几乎没有关系 - 现代编译器可以自由内联(或不内联)函数 "behind you back",这些构成了优化技术的一部分。
代码转换(包括内联)在C++"as-if" rule下完成,基本上就是编译器想怎么转换就怎么转换,只要由于执行是 "as-if" 原始代码按编写执行。此规则推动了 C++ 中的优化。
也就是说,一旦函数的地址被获取,它就必须存在(即地址必须有效)。这可能意味着它不再内联,但仍然可以(优化器将应用适当的分析)。
So why can a pointer exist to a inline function, given that there is no fixed memory address of inline functions?
不,这只是一个提示,主要与链接有关,而不是实际的内联。这助长了可以说是当前的主要用途,即在头文件中定义函数。
Should it not print different values of address of
n
each timefunc()
is called?
可能,n
是一个局部变量,基于函数执行时的堆栈位置。也就是说,函数inline
,它涉及链接,链接器将合并翻译单元上的函数。
如
... that if the example is changed to
static int n
, then every call to the function must print a constant value (in a single program run of course) ... and that is true whether or not the code is inlined or not.
这又是链接要求对局部变量的影响 n
。
内联函数并不总是内联的。它只是表示程序员希望内联此函数。 The compiler is allowed to inline any function, regarless of whether inline keyword was used or not.
如果使用函数的地址,函数很可能没有内联在最终的可执行文件中,至少在 GCC 中是这样:
When a function is both inline and static, if all calls to the function are integrated into the caller, and the function's address is never used, then the function's own assembler code is never referenced.
你读旧material。现在使用 inline
的主要原因是允许 header 文件中的函数体。将 inline
关键字与函数一起使用向链接器发出信号,表明可以组合跨翻译单元的所有函数实例;在多个单元包含的 header 中具有 non-inline 函数会由于违反单一定义规则而导致未定义的行为。
C++17还增加了inline variables,它和属性一样,变量可以定义在一个header中,所有的定义都是由链接器组合而不是导致 ODR 违规。
您与 "code getting copied to the calling function" 谈论的内容称为 inlining 并且独立于 inline
关键字。编译器将根据优化设置决定是否对 non-inline 函数和内联函数执行此操作。
1) Why pointers to inline functions are allowed in c++?
因为内联函数和其他函数一样,指向它们是您可以用函数做的事情之一。内联函数在这方面并不特别。
I have read that code of inline functions just get copied to the function calling statement and there is no compile time memory allocations in inline functions.
您(也许还有您读过的 material)混合了两个相关且命名相似的概念。
一个内联函数在所有使用它的翻译单元中定义,而一个non-inline函数只在一个翻译单元中根据一个定义规则的要求定义。这就是函数的内联声明的含义;它放宽了单一定义规则,但也提出了在所有使用它的翻译单元中定义的额外要求(如果 odr 没有放宽,这是不可能的)。
内联扩展(或内联)是一种优化,通过将被调用函数复制到调用者的框架中来避免函数调用。函数调用可以内联扩展,无论该函数是否已声明为内联。并且声明为内联的函数不一定是展开内联的。
但是,无法在未定义的翻译单元中内联扩展函数(除非 link 时间优化执行扩展)。因此,在内联声明允许的所有 TU 中定义的要求,也通过允许在调用它的所有 TU 中定义函数,使函数的内联扩展成为可能。但不能保证优化。
2) Should it not print different values of address of n each time func() is called?
内联扩展确实会导致局部变量位于调用者的框架中,是的。但如果调用来自不同的帧,则无论扩展如何,它们的位置都会不同。
通常会为任何已内联扩展的函数生成一个常规 non-expanded 版本。如果函数的地址被获取,它将指向那个 non-expanded 函数。如果编译器可以证明对函数的所有调用都是内联的,编译器可能会选择根本不提供 non-expanded 版本。这要求该函数具有内部 linkage,并且获取函数的地址通常会使此类证明变得非常困难或不可能。
除了已经说过的一点,inline
函数实际上不需要内联(许多没有 inline
的函数被现代编译器内联 ) , 通过函数指针 内联 调用也是完全可以想象的。示例:
#include <iostream>
int foo(int (*fun)(int), int x) {
return fun(x);
}
int succ(int n) {
return n+1;
}
int main() {
int c=0;
for (int i=0; i<10000; ++i) {
c += foo(succ, i);
}
std::cout << c << std::endl;
}
在这里,foo(succ, i)
可以 作为一个整体 内联到 i+1
。确实这似乎发生了†:g++ -O3 -S
为 foo
和 succ
函数生成代码
_Z3fooPFiiEi:
.LFB998:
.cfi_startproc
movq %rdi, %rax
movl %esi, %edi
jmp *%rax
.cfi_endproc
.LFE998:
.size _Z3fooPFiiEi, .-_Z3fooPFiiEi
.p2align 4,,15
.globl _Z4succi
.type _Z4succi, @function
_Z4succi:
.LFB999:
.cfi_startproc
leal 1(%rdi), %eax
ret
.cfi_endproc
但随后它为 main
生成代码, 从未引用 任何一个,而是只包含一个新的专用 _GLOBAL__sub_I__Z3fooPFiiEi
:
.LFE999:
.size _Z4succi, .-_Z4succi
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB1000:
.cfi_startproc
movdqa .LC1(%rip), %xmm4
xorl %eax, %eax
pxor %xmm1, %xmm1
movdqa .LC0(%rip), %xmm0
movdqa .LC2(%rip), %xmm3
jmp .L5
.p2align 4,,10
.p2align 3
.L8:
movdqa %xmm2, %xmm0
.L5:
movdqa %xmm0, %xmm2
addl , %eax
paddd %xmm3, %xmm0
cmpl 00, %eax
paddd %xmm0, %xmm1
paddd %xmm4, %xmm2
jne .L8
movdqa %xmm1, %xmm5
subq , %rsp
.cfi_def_cfa_offset 32
movl $_ZSt4cout, %edi
psrldq , %xmm5
paddd %xmm5, %xmm1
movdqa %xmm1, %xmm6
psrldq , %xmm6
paddd %xmm6, %xmm1
movdqa %xmm1, %xmm7
movd %xmm7, 12(%rsp)
movl 12(%rsp), %esi
call _ZNSolsEi
movq %rax, %rdi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
xorl %eax, %eax
addq , %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE1000:
.size main, .-main
.p2align 4,,15
.type _GLOBAL__sub_I__Z3fooPFiiEi, @function
_GLOBAL__sub_I__Z3fooPFiiEi:
.LFB1007:
.cfi_startproc
subq , %rsp
.cfi_def_cfa_offset 16
movl $_ZStL8__ioinit, %edi
call _ZNSt8ios_base4InitC1Ev
movl $__dso_handle, %edx
movl $_ZStL8__ioinit, %esi
movl $_ZNSt8ios_base4InitD1Ev, %edi
addq , %rsp
.cfi_def_cfa_offset 8
jmp __cxa_atexit
.cfi_endproc
.LFE1007:
.size _GLOBAL__sub_I__Z3fooPFiiEi, .-_GLOBAL__sub_I__Z3fooPFiiEi
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I__Z3fooPFiiEi
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
所以在这种情况下,实际程序甚至不包含指向 succ
的函数指针——编译器发现这个指针无论如何总是指向同一个函数,因此能够消除整个事情没有改变行为。当您经常通过函数指针调用小函数时,这可以大大提高性能。这是函数式语言中相当普遍的技术; O'Caml 和 Haskell 等语言的编译器充分利用了这种优化。
†免责声明:我的组装技能几乎不存在。我可能是在胡说八道。