web-dev-qa-db-fra.com

Comment trouver le nom de la fonction appelante?

J'utilise PRETTY_FUNCTION pour afficher le nom de la fonction actuelle, mais j'ai réimplémenté certaines fonctions et j'aimerais savoir quelles fonctions les appellent.

En C++, comment puis-je obtenir le nom de la fonction de la routine d'appel?

43
Phil Hannent

Voici deux options:

  1. Vous pouvez obtenir une trace de pile complète (y compris le nom, le module et l'offset de la fonction appelante) avec les versions récentes de glibc avec les fonctions de backtrace GN . Voir ma réponse ici pour les détails. C'est probablement la chose la plus simple.

  2. Si ce n'est pas exactement ce que vous recherchez, vous pouvez essayer libunwind , mais cela impliquera plus de travail.

Gardez à l'esprit que ce n'est pas quelque chose que vous pouvez savoir statiquement (comme avec PRETTY_FUNCTION); vous devez en fait parcourir la pile pour savoir quelle fonction vous a appelée. Donc, ce n'est pas quelque chose qui vaut vraiment la peine d'être fait dans les printfs de débogage ordinaires. Si vous souhaitez effectuer un débogage ou une analyse plus sérieux, cela peut vous être utile.

21
Todd Gamblin

Voici une solution que vous pouvez souvent utiliser. Il a l'avantage de ne nécessiter aucune modification du code de fonction réel (pas d'ajout d'appels aux fonctions de stackwalk, de modification des paramètres pour passer des noms de fonction, ou de liaison à des bibliothèques supplémentaires.). Pour le faire fonctionner, il vous suffit d'utiliser un peu de magie du préprocesseur:

Exemple simple

// orignal function name was 'FunctionName'
void FunctionNameReal(...)
{
  // Do Something
}

#undef FunctionName
#define FunctionName printf("Calling FunctionName from %s\n",__FUNCTION__);FunctionNameReal

Vous devez renommer votre fonction temporairement, mais consultez la note ci-dessous pour plus de suggestions. Cela se traduira par une instruction printf() à chaque point d'appel de la fonction. De toute évidence, vous devez prendre certaines dispositions si vous appelez une fonction membre ou si vous devez capturer la valeur de retour (comme passer l'appel de fonction et__FUNCTION__à une valeur personnalisée) fonction qui renvoie le même type ...), mais la technique de base est la même. Vous voudrez peut-être utiliser __LINE__ Et __FILE__ Ou d'autres macros de préprocesseur selon le compilateur que vous possédez. (Cet exemple est spécifiquement pour MS VC++, mais fonctionne probablement dans d'autres.)

En outre, vous souhaiterez peut-être mettre quelque chose comme ça dans votre en-tête entouré de gardes #ifdef Pour l'activer sous condition, ce qui peut également gérer le changement de nom de la fonction réelle.

MISE À JOUR [2012-06-21]

J'ai reçu une demande pour étendre ma réponse. En fait, mon exemple ci-dessus est un peu simpliste. Voici quelques exemples de compilation complète de la gestion de cela, en utilisant C++.

Exemple de source complète avec une valeur de retour

L'utilisation d'un class avec operator() rend cela assez simple. Cette première technique fonctionne pour les fonctions autonomes avec et sans valeurs de retour. operator() a juste besoin de refléter le même retour que la fonction en question et d'avoir des arguments correspondants.

Vous pouvez le compiler avec g++ -o test test.cpp Pour une version sans rapport et g++ -o test test.cpp -DREPORT Pour une version qui affiche les informations sur l'appelant.

#include <iostream>

int FunctionName(int one, int two)
{
  static int calls=0;
  return (++calls+one)*two;
}

#ifdef REPORT
  // class to capture the caller and print it.  
  class Reporter
  {
    public:
      Reporter(std::string Caller, std::string File, int Line)
        : caller_(Caller)
        , file_(File)
        , line_(Line)
      {}

      int operator()(int one, int two)
      {
        std::cout
          << "Reporter: FunctionName() is being called by "
          << caller_ << "() in " << file_ << ":" << line_ << std::endl;
        // can use the original name here, as it is still defined
        return FunctionName(one,two);
      }
    private:
      std::string   caller_;
      std::string   file_;
      int           line_;

  };

// remove the symbol for the function, then define a new version that instead
// creates a stack temporary instance of Reporter initialized with the caller
#  undef FunctionName
#  define FunctionName Reporter(__FUNCTION__,__FILE__,__LINE__)
#endif


void Caller1()
{
  int val = FunctionName(7,9);  // <-- works for captured return value
  std::cout << "Mystery Function got " << val << std::endl;
}

void Caller2()
{
  // Works for inline as well.
  std::cout << "Mystery Function got " << FunctionName(11,13) << std::endl;
}

int main(int argc, char** argv)
{
  Caller1();
  Caller2();
  return 0;
}

Exemple de sortie (rapport)

Reporter: FunctionName() is being called by Caller1() in test.cpp:44
Mystery Function got 72
Reporter: FunctionName() is being called by Caller2() in test.cpp:51
Mystery Function got 169

Fondamentalement, partout où FunctionName se produit, il le remplace par Reporter(__FUNCTION__,__FILE__,__LINE__), dont l'effet net est que le préprocesseur écrit un objet instancié avec un appel immédiat à la fonction operator() . Vous pouvez afficher le résultat (en gcc) des substitutions du préprocesseur avec g++ -E -DREPORT test.cpp. Caller2 () devient ceci:

void Caller2()
{
  std::cout << "Mystery Function got " << Reporter(__FUNCTION__,"test.cpp",51)(11,13) << std::endl;
}

Vous pouvez voir que __LINE__ Et __FILE__ Ont été remplacés. (Je ne sais pas pourquoi __FUNCTION__ Apparaît toujours dans la sortie pour être honnête, mais la version compilée rapporte la bonne fonction, donc elle a probablement quelque chose à voir avec le prétraitement multi-passes ou un bogue gcc.)

Exemple de source complète avec une fonction membre de classe

C'est un peu plus compliqué, mais très similaire à l'exemple précédent. Au lieu de simplement remplacer l'appel à la fonction, nous remplaçons également la classe.

Comme dans l'exemple ci-dessus, vous pouvez le compiler avec g++ -o test test.cpp Pour une version sans rapport et g++ -o test test.cpp -DREPORT Pour une version qui affiche les informations sur l'appelant.

#include <iostream>

class ClassName
{
  public:
    explicit ClassName(int Member)
      : member_(Member)
      {}

    int FunctionName(int one, int two)
    {
      return (++member_+one)*two;
    }

  private:
    int member_;
};

#ifdef REPORT
  // class to capture the caller and print it.  
  class ClassNameDecorator
  {
    public:
      ClassNameDecorator( int Member)
        : className_(Member)
      {}

      ClassNameDecorator& FunctionName(std::string Caller, std::string File, int Line)
      {
        std::cout
          << "Reporter: ClassName::FunctionName() is being called by "
          << Caller << "() in " << File << ":" << Line << std::endl;
        return *this;
      }
      int operator()(int one, int two)
      {
        return className_.FunctionName(one,two);
      }
    private:
      ClassName className_;
  };


// remove the symbol for the function, then define a new version that instead
// creates a stack temporary instance of ClassNameDecorator.
// FunctionName is then replaced with a version that takes the caller information
// and uses Method Chaining to allow operator() to be invoked with the original
// parameters.
#  undef ClassName
#  define ClassName ClassNameDecorator
#  undef FunctionName
#  define FunctionName FunctionName(__FUNCTION__,__FILE__,__LINE__)
#endif


void Caller1()
{
  ClassName foo(21);
  int val = foo.FunctionName(7,9);  // <-- works for captured return value
  std::cout << "Mystery Function got " << val << std::endl;
}

void Caller2()
{
  ClassName foo(42);
  // Works for inline as well.
  std::cout << "Mystery Function got " << foo.FunctionName(11,13) << std::endl;
}

int main(int argc, char** argv)
{
  Caller1();
  Caller2();
  return 0;
}

Voici un exemple de sortie:

Reporter: ClassName::FunctionName() is being called by Caller1() in test.cpp:56
Mystery Function got 261
Reporter: ClassName::FunctionName() is being called by Caller2() in test.cpp:64
Mystery Function got 702

Les points forts de cette version sont une classe qui décore la classe d'origine et une fonction de remplacement qui renvoie une référence à l'instance de classe, permettant à la operator() de faire l'appel de la fonction réelle.

J'espère que cela aide quelqu'un!

45
Aaron

Avec la version GCC ≥ 4,8, vous pouvez utiliser __builtin_FUNCTION - à ne pas confondre avec __FUNCTION__ et similaire - il semble être un peu obscur.

Exemple:

#include <cstdio>

void foobar(const char* str = __builtin_FUNCTION()){
    std::printf("called by %s\n", str);
}

int main(){
    foobar();
    return 0;
}

production:

called by main

exemple sur WandBox

18

À moins que la question ne contienne plus que ce que vous avez explicitement demandé, renommez simplement la fonction et laissez le compilateur/éditeur de liens vous dire où elle est appelée.

3
bgbarcus

Variation de Aaron réponse. Je ne sais pas si cette réponse a ce problème, mais lorsque vous effectuez une #define function, il devient une variable globale, puis, si votre projet a plusieurs classes avec le même nom de fonction de classe membre, toutes les classes verront leur nom de fonction redéfini sur la même fonction.

#include <iostream>

struct ClassName {
    int member;
    ClassName(int member) : member(member) { }

    int secretFunctionName(
              int one, int two, const char* caller, const char* file, int line) 
    {
        std::cout << "Reporter: ClassName::function_name() is being called by "
                << caller << "() in " << file << ":" << line << std::endl;

        return (++member+one)*two;
    }
};

#define unique_global_function_name(first, second) \
        secretFunctionName(first, second, __FUNCTION__,__FILE__,__LINE__)

void caller1() {
    ClassName foo(21);
    int val = foo.unique_global_function_name(7, 9);
    std::cout << "Mystery Function got " << val << std::endl;
}

void caller2() {
    ClassName foo(42);
    int val = foo.unique_global_function_name(11, 13);
    std::cout << "Mystery Function got " << val << std::endl;
}

int main(int argc, char** argv) {
    caller1();
    caller2();
    return 0;
}

Résultat:

Reporter: ClassName::function_name() is being called by caller1() in D:\test.cpp:26
Mystery Function got 261
Reporter: ClassName::function_name() is being called by caller2() in D:\test.cpp:33
Mystery Function got 702
2
user

Dans la première approximation, grep juste la base de code pour les noms de fonction. Vient ensuite Doxygen, puis la journalisation dynamique (toutes deux discutées par d'autres).

1
Arkadiy

Vous pouvez utiliser ce code pour suivre les lieux de contrôle des n derniers points de votre programme. Utilisation: voir fonction principale ci-dessous.

// What: Track last few lines in loci of control, gpl/moshahmed_at_gmail
// Test: gcc -Wall -g -lm -std=c11 track.c
#include <stdio.h>
#include <string.h>

#define _DEBUG
#ifdef _DEBUG
#define lsize 255 /* const int lsize=255; -- C++ */
struct locs {
  int   line[lsize];
  char *file[lsize];
  char *func[lsize];
  int  cur; /* cur=0; C++ */
} locs;

#define track do {\
      locs.line[locs.cur]=__LINE__ ;\
      locs.file[locs.cur]=(char*)__FILE__ ;\
      locs.func[locs.cur]=(char*) __builtin_FUNCTION() /* __PRETTY_FUNCTION__ -- C++ */ ;\
      locs.cur=(locs.cur+1) % lsize;\
  } while(0);

void track_start(){
  memset(&locs,0, sizeof locs);
}

void track_print(){
  int i, k;
  for (i=0; i<lsize; i++){
    k = (locs.cur+i) % lsize;
    if (locs.file[k]){
      fprintf(stderr,"%d: %s:%d %s\n",
        k, locs.file[k],
        locs.line[k], locs.func[k]);
    }
  }
}
#else
#define track       do {} while(0)
#define track_start() (void)0
#define track_print() (void)0
#endif


// Sample usage.
void bar(){ track ; }
void foo(){ track ; bar(); }

int main(){
  int k;
  track_start();
  for (k=0;k<2;k++)
    foo();
  track;
  track_print();
  return 0;
} 
1
mosh

Vous voulez probablement les noms de toutes les fonctions qui pourraient potentiellement les appeler. Il s'agit essentiellement d'un ensemble d'arêtes dans le graphe d'appel. doxygen peut générer le graphe d'appel, puis il suffit simplement de regarder les bords entrants de votre nœud de fonctions.

1
MSalters