7
votes

Comment effectuer un retour d'une valeur de la fonction _calling_?

J'aimerais pouvoir forcer une "double rendement", c'est-à-dire une fonction qui force un retour de la fonction appeler (oui, je sais qu'il n'y a pas toujours de vrai Fonction d'appel, etc.) Évidemment, je m'attends à pouvoir le faire en manipulant la pile, et je suppose qu'il est possible au moins d'une manière non portable de la machine à la machine. La question est de savoir si cela peut être fait relativement proprement et de manière portant.

Pour donner un morceau de code concret à remplir, je veux écrire la fonction xxx

de sorte que La fonction suivante xxx

retourne, disons, 1 ; et le long calcul n'est pas effectué. Supposons que foo () peut supposer qu'il ne s'appelle que par une fonction avec la signature de la barre, c'est-à-dire un int (int) (et sait donc spécifiquement ce que son type de retour de l'appelant est ).

Notes:

  • S'il vous plaît ne me confondez pas comment c'est la mauvaise pratique, je pose une question de curiosité.
  • La fonction d'appel (dans l'exemple, bar () ) ne doit pas être modifié. Il ne sera pas au courant de ce que la fonction appelée est à la hauteur. (Encore dans l'exemple, seul le bit / * magique * / peut être modifié).
  • Si cela vous aide, vous pouvez supposer qu'aucun inlinage est en cours (une hypothèse irréaliste peut-être).

4 commentaires

Ce n'est pas seulement une mauvaise pratique; Ce n'est pas possible sans modifier bar également ou faire des tours stupides avec des exceptions. Ce genre de chose est entièrement ce que le mouvement de programmation structuré se battait contre (raisonnablement et avec succès, je devrais ajouter).


Comment la fonction interne est-elle censée savoir quel est le type de retour de l'appelant ?!


"Relativement proprement et de manière significative" est très subjectif. Voter pour fermer comme base d'opinion.


@Kerreksb: Cela découle de l'hypothèse concernant le type de l'appelant. Je l'ai rendue plus explicite maintenant.


4 Réponses :


8
votes

La question est de savoir si cela peut être fait relativement proprement et de manière portante.

La réponse est qu'elle ne peut pas.

Mis à part tous les détails non portables de la manière dont la pile d'appel est implémentée sur différents systèmes, supposons FOO est inliné dans bar . Ensuite (généralement), il n'aura pas son propre cadre de pile. Vous ne pouvez pas parler de manière proprement ou de manière portant une ingénierie inverse d'une version "double" ou "N-Times", car la pile d'appel réelle ne ressemble pas nécessairement à ce que vous attendez basé sur les appels de l'Abstrait C ou C ++ Machine.

Les informations dont vous avez besoin pour pirater ceci sont probablement (aucune garantie) disponible avec des informations de débogage. Si un débogueur va présenter la pile d'appels "logique" à son utilisateur, y compris les appels inliner, il doit y avoir suffisamment d'informations disponibles pour localiser l'appelant "Deux niveaux en haut". Ensuite, vous devez imiter le code de sortie de fonction spécifique à la plate-forme pour éviter de ne rien briser. Cela nécessite de restaurer tout ce que la fonction intermédiaire restaurerait normalement, ce qui pourrait ne pas être facile à comprendre même avec des informations de débogage, car le code pour le faire est dans bar quelque part. Mais je soupçonne que, puisque le débogueur peut montrer l'état de cette fonction d'appel, puis au moins en principe, les informations de débogage contiennent probablement suffisamment d'informations pour la restaurer. Revenez ensuite à cet emplacement de l'appelant d'origine (qui pourrait être atteint avec un saut explicite, ou en manipulant partout où votre plate-forme conserve son adresse de retour et effectue un rendement normal). Tout cela est très sale et très non portable, d'où ma réponse "non".

Je suppose que vous savez déjà que vous pouvez utiliser des exceptions ou setJMP / / LONDJMP . bar ou l'appelant de bar (ou les deux) nécessiterait de coopérer avec cela et d'accord avec foo comment la "valeur de retour> " est stocké. Alors je suppose que ce n'est pas ce que tu veux. Mais si la modification de l'appelant de bar est acceptable, vous pouvez faire quelque chose comme ça. Ce n'est pas joli, mais il suffit de fonctionner (en C ++ 11, en utilisant des exceptions). Je laisserai cela pour savoir comment comprendre comment le faire en c utilise setingjmp / longjmp et avec une signature de fonction fixe au lieu d'un modèle: < Pré> xxx

Enfin, pas une "conférence de mauvaise pratique", mais un avertissement pratique - en utilisant tout astuce pour retourner tôt ne va pas en général avec le code écrit en C ou écrit en C ++ qui ne s'attend pas à une exception à laisser foo . La raison est que bar aurait pu attribuer certaines ressources ou mettre une certaine structure dans un état qui enfreint ses invariants avant d'appeler foo , avec l'intention de libérer cette ressource ou de restaurer la invariant dans le code suivant l'appel. Donc, pour les fonctions générales bar , si vous ignorez le code dans bar , vous pouvez provoquer une fuite de mémoire ou un état de données non valide. Le seul moyen d'éviter cela en général, peu importe ce qui est dans bar , consiste à autoriser le reste de la barre à exécuter. Bien sûr, si bar est écrit en C ++ avec l'attente que foo pourrait lancer, il aura utilisé RAII pour le code de nettoyage et il s'exécutera lorsque vous lancez. LONDJMP ING Over ADESTRUCTOR a un comportement non défini, cependant, vous devez donc décider avant de commencer si vous avez affaire à C ++ ou avec c.


0 commentaires

2
votes

Le seul moyen propre est de modifier vos fonctions:

bool foo(int x) {
    if (skip) return true;
    return false;
}

int bar(int x) {
    if (foo(x)) return 1;
    /* long computation here */
    return 0;
}


1 commentaires

@Einpoklum: Alors évidemment, tu es assez vissé. :) Un sous-programme ne peut pas se contrôler de son appelant de manière fiable et de manière géniale avec ce type de précision. (Les quelques-uns qui semblent, soit de finir par dos à l'appelant à un moment donné, soit tuez le processus afin que le messentiré soit discuté.) Vous devez savoir comment l'appelant fonctionne et si < CODE> BAR Peut être une fonction arbitraire qui prend et renvoie un int, vous ne savez pas comment la pile est aménagée.



4
votes

Il existe deux manières portables de le faire, mais les deux nécessitent une assistance de la fonction de l'appelant. Pour C, c'est SETJMP + LONDJMP. Pour C ++, il s'agit d'une utilisation des exceptions (essayer + attraper + lancer). Les deux sont assez similaires dans la mise en œuvre (en substance, une implémentation d'exceptions précoce était basée sur SETJMP). Et, il n'y a certainement pas de moyen portable de faire cela sans la sensibilisation à la fonction de l'appelant ...


0 commentaires

1
votes

Modifier votre fonction vide FOO () code> Pour renvoyer un Booléen OUI / NON, puis enveloppez-le dans une macro du même nom:

    extern bool (foo)(int);


0 commentaires