54 votes

Pourquoi les pointeurs vers les fonctions en ligne sont-ils autorisés?

J'ai deux questions:

1) Pourquoi sont des pointeurs sur des fonctions en ligne autorisé en C++? J'ai lu que le code des fonctions inline juste copié à l'appel de la fonction instruction et il n'est pas au moment de la compilation de l'allocation de mémoire dans les fonctions inline. Alors pourquoi un pointeur existe une fonction en ligne, étant donné qu'il n'y pas d'adresse de mémoire pour les fonctions inline?

2) Considérez le code ci-dessous:

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

Ne devrait-on pas imprimer les différentes valeurs de l'adresse de l' n chaque fois func() est appelé? [Parce que je pense que chaque fois que la fonction inline code est copié, réaffectation des variables locales doit être fait (alors que dans le cas des fonctions normales, réinitialisation)]

Je suis un débutant et j'ai demandé à cette question pour le bien de mon concept de renforcement. S'il vous plaît corrigez-moi si je me trompe, n'importe où.

63voto

user2079303 Points 4916

1) Pourquoi les pointeurs de fonctions en ligne sont autorisés en c++?

Parce que les fonctions inline sont des fonctions comme toutes les autres, et en pointant vers eux est l'une des choses que vous pouvez faire avec les fonctions. Les fonctions Inline ne sont pas spéciales à cet égard.

J'ai lu que le code des fonctions inline simplement copiés sur la fonction d'instruction d'appel et il n'y a aucun moment de la compilation, les allocations de mémoire dans les fonctions inline.

Vous (et peut-être le matériel que vous avez lu) ont mélangé les deux et nommé de la même manière les concepts.

Une fonction en ligne est définie dans toutes les unités de traduction qui l'utilisent, alors qu'un non-inline fonction est définie dans une unité de traduction dans la mesure requise par la définition de la règle. Qu'est ce qu'une ligne de la déclaration d'une fonction; il détend l'une définition de la règle, mais donne également être définies pour toutes les unités de traduction qui l'utilisent (ce qui n'aurait pas été possible si l'odr n'était pas détendu).

Inline expansion (ou l'in-lining) est une optimisation, le cas d'un appel de fonction est évitée par la copie de la fonction appelée dans le cadre de l'appelant. Un appel de fonction peut être étendue à la volée, si la fonction a été déclarée, en ligne ou pas. Et une fonction qui ont été déclarées en ligne n'est pas nécessairement élargi en ligne.

Cependant, une fonction ne peut pas être élargi en ligne dans une unité de traduction, où il n'est pas défini (à moins que link time optimization effectue l'expansion). Par conséquent, l'obligation d'être définie dans tous TUs que la ligne de la déclaration permet, rend également possible la ligne d'extension de la fonction en permettant à la fonction définie dans tous les Syndicats qui les invoquent. Mais l'optimisation n'est pas garanti.

2) faut-il ne pas imprimer les différentes valeurs de l'adresse de n à chaque fois que la touche func() est appelée?

Inline expansion provoque les variables locales à être situé dans le cadre de l'appelant, oui. Mais leur emplacement sera différent, indépendamment de l'expansion, si les appels proviennent de sections distinctes.

Il y a généralement un non-version élargie généré d'une fonction qui a été élargi en ligne. Si l'adresse d'une fonction est prise, il sera fait pour que les non-élargissement de la fonction. Si le compilateur peut prouver que tous les appels à une fonction inline, le compilateur peut choisir de ne pas fournir les non-version étendue à tous. Cela nécessite que la fonction a une liaison interne, et en prenant l'adresse de la fonction fait généralement preuve très difficile, voire impossible.

27voto

Niall Points 6133

L' inline mot-clé était à l'origine une astuce pour le compilateur que vous le programmeur pense que cette fonction est un candidat pour inline - le compilateur n'est pas nécessaire pour honorer cette.

Dans l'usage moderne, il a à peu près rien à voir avec inline - les compilateurs modernes librement en ligne (ou pas) les fonctions "derrière vous", elles font partie des techniques d'optimisation.

Code transformations (y compris inlining) se fait sous le "comme-si" la règle en C++, ce qui signifie que le compilateur peut transformer le code comme il veut, tant que l'exécution est "comme-si" le code original a été exécuté comme à l'écrit. Cette règle combustibles optimisations en C++.

Cela dit, une fois qu'une adresse est prise de fonction, il est nécessaire à l'existence (c'est à dire l'adresse est nécessaire pour être valide). Cela peut signifier qu'il n'est plus inline, mais pourrait encore être (l'optimiseur va appliquer l'analyse appropriée).

Alors pourquoi un pointeur existe une fonction inline, étant donné qu'il n'existe pas de mémoire fixe l'adresse de fonctions inline?

Non, il n'est qu'une indication et est largement lié à l'établissement de liens et non réel, d'inlining. Cette carburants, ce qui est sans doute la principale utilisation actuelle, de définir des fonctions dans les fichiers d'en-tête.

Ne devrait-on pas imprimer les différentes valeurs de l'adresse de l' n chaque fois func() est appelé?

Il pourrait, n est une variable locale, basée sur l'emplacement de pile lorsque la fonction s'exécute. Cela dit, la fonction inline, il concerne le lien, l'éditeur de liens de fusionner les fonctions sur les unités de traduction.


Comme indiqué dans les commentaires;

... que si l'exemple est changé en static int n, puis à chaque appel de la fonction doit imprimer une valeur constante (dans un seul programme à exécuter, bien sûr) ... et c'est vrai si le code est incorporé ou non.

C'est, encore une fois, l'effet de la liaison condition sur la variable locale n.

14voto

Matt McNabb Points 14273

Vous lisez de vieux matériaux. La principale raison de l'utilisation d' inline aujourd'hui est de permettre aux corps de fonction dans les fichiers d'en-tête. L'utilisation d' inline mot-clé avec une fonction de signaux à l'éditeur de liens que toutes les instances de la fonction à travers les unités de traduction peuvent être combinées; avoir un non-fonction inline dans un en-tête est inclus à partir de plusieurs unités provoque un comportement indéterminé en raison d'une Définition de la violation des règles.

C++17 ajoute également inline variables, qui ont la même propriété que la variable peut être définie dans un en-tête, et toutes les définitions sont combinés par l'éditeur de liens au lieu de causer ODR violation.

Les choses dont vous parlez avec "code obtenir copié à l'appel de la fonction" inline et est indépendant de l' inline mot-clé. Le compilateur va décider si ou de ne pas le faire, basé sur les paramètres d'optimisation, pour les non-fonctions en ligne ainsi que des fonctions inline.

5voto

Ville-Valtteri Points 3536

Les fonctions Inline sont pas toujours inline. Il vient de signaux que le programmeur voudrais que cette fonction inline. Le compilateur est autorisé à en ligne des fonction, regarless de si mot-clé inline a été utilisé ou non.

Si l'adresse de la fonction est utilisée, la fonction est probablement pas incorporé dans l'exécutable final, au moins dans GCC:

Lorsqu'une fonction est à la fois en ligne et statique, si tous les appels à la fonction sont intégrés dans l'appelant, et l'adresse de la fonction n'est jamais utilisé, alors la fonction propre code assembleur n'est jamais mentionné.

La documentation de GCC

4voto

leftaroundabout Points 23679

En dehors de la déjà dit point qu'un inline fonction n'a pas besoin d'être réellement inline (et de nombreuses fonctions sans inline sont intégrées par les compilateurs modernes), il est également tout à fait possible de l'inclure un appel à l'aide d'un pointeur de fonction. Exemple:

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

Ici, foo(succ, i) pourrait dans son ensemble est insérée juste i+1. Et, de fait qui semble se produire: g++ -O3 -S produit du code pour l' foo et succ fonctions

_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

Mais alors, il génère du code pour main qui ne se réfère jamais à l'un de ces, à la place inclut une nouvelle spécialisées _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

Dans ce cas, le programme ne contient même pas un pointeur de fonction en pointant succ – le compilateur a découvert que ce pointeur serait toujours se référer à la même fonction, de toute façon, et était donc en mesure d'éliminer la totalité de la chose sans en changer le comportement. Cela peut améliorer les performances d'un lot, lorsque vous vous adressez souvent de petites fonctions par le biais de pointeurs de fonction. Ce qui est assez répandue technique dans les langages fonctionnels; les compilateurs pour les langages comme O'Caml et Haskell faire un grand usage de ce genre d'optimisation.


Avertissement: mon assemblée compétences sont proches, voire inexistante. Je pourrais bien parler ordures ici.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X