55

I have two questions:

1) Why are pointers to inline functions allowed in C++? I have read that the code of inline functions just gets copied to the function call statement and there is no compile-time memory allocation in inline functions. So why can a pointer exist to an inline function, given that there is no fixed memory address for inline functions?

2) Consider the code below:

inline void func()    
{
    int n=0;
    cout<<(&n);
} 

Should it not print different values of the address of n each time func() is called? [Because I think that every time inline function code is copied, reallocation of the local variables must be done (whereas in the case of normal functions, reinitialisation takes place)]

I am a beginner and I asked this question for the sake of my concept strengthening. Please correct me if I am wrong anywhere.

psmears
  • 26,070
  • 4
  • 40
  • 48
Madhuchhanda Mandal
  • 917
  • 1
  • 7
  • 20
  • 39
    1) `inline` in modern c++ has very little to do with whether a function is inlined by the compiler – user657267 Jan 19 '17 at 08:13
  • 9
    There is no guarantee than inline function will be in fact inlined. This is a recommendation for the compiler, not a demand. – Ari0nhh Jan 19 '17 at 08:14
  • 9
    output for `&n` may or may not differ, regardless of whether you use `inline` – M.M Jan 19 '17 at 08:17
  • Can you please point out the reason?@M.M – Madhuchhanda Mandal Jan 19 '17 at 08:24
  • 5
    @MadhuchhandaMandal every `n` is a different variable (automatic variable is created and destroyed each time its scope is entered and exited), it will depend on various details of the compilation as to whether different instances happen to give the same output – M.M Jan 19 '17 at 08:26
  • @user657267 so there is no point in using `inline` anymore? – Celeritas Jan 19 '17 at 09:14
  • Would the following scenario work? The compiler creates the function `func()` as if it wasn't inlined. Thus, operations on pointers to `func()` are valid in the code. However, plain calls to `func()` can still be inlined. – Dohn Joe Jan 19 '17 at 09:47
  • 4
    @Celeritas: still critical for header-only code, it's just mostly unrelated to actual function inlining. – Mooing Duck Jan 19 '17 at 18:28
  • @user657267 same as `register` keyword, but `register` still prevents a variable from being taken address, shouldn't `inline` be the same? – phuclv Jan 20 '17 at 09:13
  • [Related question](http://stackoverflow.com/q/29796264/1782465) – Angew is no longer proud of SO Jan 20 '17 at 09:29
  • @M.M Surely the value of `&n` will depend more on the run-time code-paths leading to each function call (and specifically the size and number of the stack-frames) rather than details of compilation. – TripeHound Jan 20 '17 at 09:59
  • @LưuVĩnhPhúc `register` was deprecated in c++11 and removed completely in c++17; in any case I don't understand what you're asking. – user657267 Jan 21 '17 at 02:11
  • @user657267 "inline in modern c++ has very little to do with whether a function is inlined by the compiler" this is incorrect. `register` in modern C/C++ has very little (if not at all) to do with whether a variable is stored in register or not. But it has the effect of preventing one to take the variable's address. So is `inline`, which should prevent the same – phuclv Jan 21 '17 at 02:52
  • @LưuVĩnhPhúc So you're saying it's counterintuitive, I suppose it is but only if you're stuck with the dated notion that `inline` is somehow related to function inlining. – user657267 Jan 21 '17 at 03:16

5 Answers5

65

1) Why pointers to inline functions are allowed in c++?

Because inline functions are functions just like any other, and pointing to them is one of the things that you can do with functions. Inline functions just aren't special in this regard.

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.

You (and perhaps the material you've read) have mixed two related and similarly named concepts.

An inline function is defined in all translation units that use it, while a non-inline function is defined in one translation unit only as required by the one definition rule. That is what an inline declaration of a function means; it relaxes the one definition rule, but also gives the additional requirement of being defined in all translation units that use it (which would not have been possible if the odr wasn't relaxed).

Inline expansion (or inlining) is an optimization, where a function call is avoided by copying the called function into the frame of the caller. A function call can be expanded inline, whether the function has been declared inline or not. And a function that has been declared inline is not necessarily expanded inline.

However, a function can not be expanded inline in a translation unit where it is not defined (unless link time optimization performs the expansion). Therefore the requirement of being defined in all TUs that the inline declaration allows, also makes possible the inline expansion of the function by allowing the function to be defined in all TUs that invoke it. But the optimization is not guaranteed.

2) Should it not print different values of address of n each time func() is called?

Inline expansion does cause the local variables to be located in the frame of the caller, yes. But their location will differ regardless of expansion if the calls originate from separate frames.

There is typically a regular non-expanded version generated of any function that has been expanded inline. If the address of a function is taken, it will point to that non-expanded function. If the compiler can prove that all calls to a function are inlined, the compiler might choose to not provide the non-expanded version at all. This requires that the function has internal linkage, and taking the address of the function typically makes such proof very difficult, or impossible.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 2
    +1 for a succinct, useful, and new to me characterization of the true meaning of `inline`: that "it relaxes the one definition rule." That's in fact even more appropos for C than for C++, because C does not have the requirement that all `inline` definitions of the same function be identical. – John Bollinger Jan 19 '17 at 20:04
  • @JohnBollinger Well, now I'm confused. Per [your answer on an old question of mine](http://stackoverflow.com/a/35234495/1858225) (which, actually, I've kept open in a tab ever since, because I still don't feel like I fully understand it), "`inline` or no `inline`, C does not permit multiple external definitions of the same name among the translation units contributing to the same program or library." So it sounds like you're saying that `inline` *does not* relax the one definition rule in C. But in your comment above, it sounds like you're saying it *does*...? – Kyle Strand Jan 19 '17 at 21:38
  • 2
    @KyleStrand, there is a fine detail here, but no inconsistency. C++ and especially C make a fine and somewhat confusing distinction between an "external definition" of a function and a definition of a function with external linkage. The difference hinges exactly on whether the function is declared `inline` in the same translation unit. If it is, then the definition is not an external definition, and therefore cannot be linked to calls from other translation units, and does not conflict with other definitions of the function, not even if the function has external linkage. – John Bollinger Jan 19 '17 at 22:36
  • @JohnBollinger Thanks for clarifying. In your last sentence, when you say "not even if the function has external linkage," what exactly would it mean for a function to have external function but for its definition to be `inline` (and thus not an internal definition)? Does this just mean that the symbol exists in (i.e. is exposed across) multiple translation units and has at least one external definition, but there exists a translation unit in which there exists a non-external definition (the `inline` one) as well? – Kyle Strand Jan 19 '17 at 22:48
  • Er, "and thus not an *external* definition", rather – Kyle Strand Jan 20 '17 at 01:14
  • @KyleStrand, in C, yes, that's it. In C++, on the other hand, if there is an inline function definition with external linkage, then there must not be an external definition of the same function anywhere in the program, though there may be other inline definitions. C++ furthermore requires that all inline definitions of the same function be identical. – John Bollinger Jan 20 '17 at 04:59
  • Compilers can and routinely do avoid emitting an out-of-line copy of a function with more than internal linkage if they can prove that its address doesn’t escape in **that** translation unit. If some other TU does need that copy, it will emit it—as a vague symbol in case more than one does so. – Davis Herring Mar 06 '23 at 17:45
27

The inline keyword was originally a hint to the compiler that you the programmer think this function is a candidate for inlining - the compiler is not required to honor this.

In modern usage, it has little to nothing to do with inlining at all - modern compilers freely inline (or not) functions "behind you back", these form part of the optimization techniques.

Code transformations (including inlining) are done under the "as-if" rule in C++, which basically means that the compiler can transform the code as it wants to, so long as the execution is "as-if" the original code was executed as written. This rule fuels optimizations in C++.

That said, once an address is taken of a function, it is required to exist (i.e. the address is required to be valid). This may mean that it is no longer inlined, but could still be (the optimizer will apply the appropriate analysis).

So why can a pointer exist to a inline function, given that there is no fixed memory address of inline functions?

No, it is only a hint and largely relates to linkage and not actual inlining. This fuels, what is arguably the main current usage, defining functions in header files.

Should it not print different values of address of n each time func() is called?

It might, the n is a local variable, based on the stack location when the function executes. That said, the function inline, it relates to linkage, the linker will merge the functions over the translation units.


As noted in the comments;

... 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.

This is, again, the effect of the linkage requirement on the local variable n.

Community
  • 1
  • 1
Niall
  • 30,036
  • 10
  • 99
  • 142
  • 12
    It could be inlined by also a non-inlined version generated. – Ed Heal Jan 19 '17 at 08:15
  • @M.M, sure, it may still be inline, gist was that it can't be "only" inline, it is required to be addressable - I've cleared that up. – Niall Jan 19 '17 at 08:19
  • 2
    calls to `func` from different parts of the program are likely to give different values for `&n` . This address relates to where "the stack" is at, not the function's address – M.M Jan 19 '17 at 08:28
  • @M.M Whoops, thats true - I miss read the `n` for being the address of the function, not the local variable. – Niall Jan 19 '17 at 08:32
  • 2
    It might be worth pointing out 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. – Martin Bonner supports Monica Jan 19 '17 at 08:39
14

You read old material. The main reason for using inline nowdays is to allow function bodies in header files. Use of inline keyword with a function signals to the linker that all instances of the function across translation units can be combined; having a non-inline function in a header that is included from multiple units causes undefined behaviour due to a One Definition Rule violation.

C++17 also adds inline variables, which have the same property that the variable can be defined in a header, and all definitions are combined by the linker instead of causing ODR violation.

The stuff you are talking about with "code getting copied to the calling function" is called inlining and is independent of the inline keyword. The compiler will decide whether or not to do this, based on optimization settings, for non-inline functions as well as inline functions.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
5

Inline functions are not always inlined. It just signals that the programmer would like this function to be inlined. The compiler is allowed to inline any function, regarless of whether inline keyword was used or not.

If the address of function is used, the function is most likely not inlined in the final executable, at least in 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.

GCC documentation

Community
  • 1
  • 1
VLL
  • 9,634
  • 1
  • 29
  • 54
  • And for the second question? [Assuming that the function has been inlined by the compiler] – Madhuchhanda Mandal Jan 19 '17 at 08:20
  • 1
    The part you quote does not imply anything about if or when inlining occurs. – molbdnilo Jan 19 '17 at 08:21
  • @MadhuchhandaMandal If the compiler sees that you are printing the address of the function, it will not inline the function. Else it would not have a valid address to print. – VLL Jan 19 '17 at 08:23
  • 2
    @Ville-ValtteriTiittanen Printing the address makes no difference on inlining. A compiler needs to generate referencable code for the function if you use its address, but it won't disable all inlining of a function just because of that. It's not an "inline everywhere or not at all" decision. – molbdnilo Jan 19 '17 at 08:29
4

Apart from the already said point that an inline function need not actually be inlined (and many functions without inline are inlined by modern compilers), it's also entirely conceivable to inline a call through a function pointer. Example:

#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;
}

Here, foo(succ, i) could as a whole be inlined to just i+1. And indeed that seems to happen: g++ -O3 -S produces code for the foo and succ functions

_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

But then it generates code for main which never refers to either of these, instead just includes a new specialised _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    $1, %eax
    paddd   %xmm3, %xmm0
    cmpl    $2500, %eax
    paddd   %xmm0, %xmm1
    paddd   %xmm4, %xmm2
    jne .L8
    movdqa  %xmm1, %xmm5
    subq    $24, %rsp
    .cfi_def_cfa_offset 32
    movl    $_ZSt4cout, %edi
    psrldq  $8, %xmm5
    paddd   %xmm5, %xmm1
    movdqa  %xmm1, %xmm6
    psrldq  $4, %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    $24, %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    $8, %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    $8, %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

So in this case the actual program does not even contain a function pointer pointing to succ – the compiler has found out that this pointer would always refer to the same function anyway, and was therefore able to eliminate the entire thing without changing the behaviour. This can improve performance a lot, when you often call small functions through function pointers. Which is quite a widespread technique in functional languages; compilers for languages like O'Caml and Haskell make great use of this kind of optimisation.


Disclaimer: my assembly skills are close to nonexistent. I might well be talking rubbish here.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319