web-dev-qa-db-fra.com

Est-il possible de créer une fonction de manière dynamique, pendant l'exécution en C++?

C++ est un langage statique compilé, les modèles sont résolus pendant la compilation, etc.

Mais est-il possible de créer une fonction en cours d’exécution qui ne soit pas décrite dans le code source et qui n’a pas été convertie en langage machine lors de la compilation, de sorte qu’un utilisateur puisse lui envoyer des données qui n’ont pas été anticipées dans le code source?

Je suis conscient que cela ne peut pas se produire de manière simple, mais cela doit être possible, de nombreux langages de programmation non compilés ne créent pas ce genre de choses de manière dynamique qui sont implémentées en C ou C++.

Peut-être que si des fabriques pour tous les types primitifs sont créées, avec des structures de données appropriées pour les organiser en objets plus complexes tels que des types d'utilisateur et des fonctions, cela est réalisable?

Toutes les informations sur le sujet ainsi que les pointeurs de documents en ligne sont les bienvenus. Merci!

EDIT: Je sais que c’est possible, c’est plutôt comme si j’étais intéressé par les détails de la mise en oeuvre :)

48
dtech

Oui , bien sûr, sans aucun outil mentionné dans les autres réponses, mais en utilisant simplement le compilateur C++ .

il suffit de suivre ces étapes depuis votre programme C++ (sous Linux, mais doit être similaire sous un autre système d'exploitation).

  1. écrire un programme C++ dans un fichier (par exemple, dans /tmp/prog.cc), à l’aide de ofstream
  2. compiler le programme via system("c++ /tmp/prog.cc -o /tmp/prog.so -shared -fPIC"); 
  3. chargez le programme dynamiquement, par ex. en utilisant dlopen()
41
Walter

Vous pouvez également simplement attribuer le bytecode directement à une fonction et le transmettre comme type de fonction, comme indiqué ci-dessous. 

par exemple.

byte[3] func = { 0x90, 0x0f, 0x1 }
*reinterpret_cast<void**>(&func)()
35
Jay

Oui, les compilateurs JIT le font tout le temps. Ils allouent une mémoire dotée de droits d'exécution spéciaux par le système d'exploitation, puis la remplissent de code, transforment le pointeur en pointeur de fonction et l'exécutent. Assez simple.

EDIT: Voici un exemple de procédure sous Linux: http://burnttoys.blogspot.de/2011/04/how-to-allocate-executable-memory-on.html

15
Milan

Ci-dessous, un exemple de compilation d'exécution C++ basé sur la méthode mentionnée précédemment (écrivez le code dans le fichier de sortie, compilez via system(), chargez via dlopen() et dlsym()). Voir aussi l'exemple d'une question connexe . La différence ici est qu'il compile de manière dynamique une classe plutôt qu'une fonction. Ceci est réalisé en ajoutant une fonction maker() de style C au code à compiler de manière dynamique. Références:

L'exemple ne fonctionne que sous Linux (Windows possède plutôt les fonctions LoadLibrary et GetProcAddress) et nécessite que le compilateur identique soit disponible sur la machine cible.

baseclass.h

#ifndef BASECLASS_H
#define BASECLASS_H
class A
{
protected:
    double m_input;     // or use a pointer to a larger input object
public:
    virtual double f(double x) const = 0;
    void init(double input) { m_input=input; }
    virtual ~A() {};
};
#endif /* BASECLASS_H */

main.cpp

#include "baseclass.h"
#include <cstdlib>      // EXIT_FAILURE, etc
#include <string>
#include <iostream>
#include <fstream>
#include <dlfcn.h>      // dynamic library loading, dlopen() etc
#include <memory>       // std::shared_ptr

// compile code, instantiate class and return pointer to base class
// https://www.linuxjournal.com/article/3687
// http://www.tldp.org/HOWTO/C++-dlopen/thesolution.html
// https://stackoverflow.com/questions/11016078/
// https://stackoverflow.com/questions/10564670/
std::shared_ptr<A> compile(const std::string& code)
{
    // temporary cpp/library output files
    std::string outpath="/tmp";
    std::string headerfile="baseclass.h";
    std::string cppfile=outpath+"/runtimecode.cpp";
    std::string libfile=outpath+"/runtimecode.so";
    std::string logfile=outpath+"/runtimecode.log";
    std::ofstream out(cppfile.c_str(), std::ofstream::out);

    // copy required header file to outpath
    std::string cp_cmd="cp " + headerfile + " " + outpath;
    system(cp_cmd.c_str());

    // add necessary header to the code
    std::string newcode =   "#include \"" + headerfile + "\"\n\n"
                            + code + "\n\n"
                            "extern \"C\" {\n"
                            "A* maker()\n"
                            "{\n"
                            "    return (A*) new B(); \n"
                            "}\n"
                            "} // extern C\n";

    // output code to file
    if(out.bad()) {
        std::cout << "cannot open " << cppfile << std::endl;
        exit(EXIT_FAILURE);
    }
    out << newcode;
    out.flush();
    out.close();

    // compile the code
    std::string cmd = "g++ -Wall -Wextra " + cppfile + " -o " + libfile
                      + " -O2 -shared -fPIC &> " + logfile;
    int ret = system(cmd.c_str());
    if(WEXITSTATUS(ret) != EXIT_SUCCESS) {
        std::cout << "compilation failed, see " << logfile << std::endl;
        exit(EXIT_FAILURE);
    }

    // load dynamic library
    void* dynlib = dlopen (libfile.c_str(), RTLD_LAZY);
    if(!dynlib) {
        std::cerr << "error loading library:\n" << dlerror() << std::endl;
        exit(EXIT_FAILURE);
    }

    // loading symbol from library and assign to pointer
    // (to be cast to function pointer later)
    void* create = dlsym(dynlib, "maker");
    const char* dlsym_error=dlerror();
    if(dlsym_error != NULL)  {
        std::cerr << "error loading symbol:\n" << dlsym_error << std::endl;
        exit(EXIT_FAILURE);
    }

    // execute "create" function
    // (casting to function pointer first)
    // https://stackoverflow.com/questions/8245880/
    A* a = reinterpret_cast<A*(*)()> (create)();

    // cannot close dynamic lib here, because all functions of the class
    // object will still refer to the library code
    // dlclose(dynlib);

    return std::shared_ptr<A>(a);
}


int main(int argc, char** argv)
{
    double input=2.0;
    double x=5.1;
    // code to be compiled at run-time
    // class needs to be called B and derived from A
    std::string code =  "class B : public A {\n"
                        "    double f(double x) const \n"
                        "    {\n"
                        "        return m_input*x;\n"
                        "    }\n"
                        "};";

    std::cout << "compiling.." << std::endl;
    std::shared_ptr<A> a = compile(code);
    a->init(input);
    std::cout << "f(" << x << ") = " << a->f(x) << std::endl;

    return EXIT_SUCCESS;
}

sortie

$ g++ -Wall -std=c++11 -O2 -c main.cpp -o main.o   # c++11 required for std::shared_ptr
$ g++ -ldl main.o -o main
$ ./main
compiling..
f(5.1) = 10.2
8
user1059432

En plus d'utiliser simplement un langage de script intégré ( Lua est idéal pour l'intégration) ou d'écrire votre propre compilateur pour C++ à utiliser au moment de l'exécution, si vous voulez vraiment utiliser C++, vous pouvez simplement utiliser un compilateur existant.

Par exemple, Clang est un compilateur C++ construit sous la forme de bibliothèques pouvant être facilement intégrées à un autre programme. Il a été conçu pour être utilisé à partir de programmes tels que les IDE qui doivent analyser et manipuler les sources C++ de différentes manières, mais en utilisant l'infrastructure du compilateur LLVM en tant que backend, il peut également générer du code à l'exécution un pointeur de fonction que vous pouvez appeler pour exécuter le code généré.

5
bames53

Regardez libtcc ; il est simple, rapide, fiable et répond à vos besoins. Je l'utilise chaque fois que j'ai besoin de compiler des fonctions C "à la volée".

Dans l'archive, vous trouverez le fichier examples/libtcc_test.c, ce qui peut vous donner une bonne longueur d'avance… .. Ce petit tutoriel pourrait également vous aider: http: //blog.mister-muffin .de/2011/10/22/discovering-tcc/ /

Posez des questions dans les commentaires si vous rencontrez des problèmes d'utilisation de la bibliothèque!

4
Mathieu Rodic

En gros, vous devrez écrire un compilateur C++ dans votre programme (pas une tâche triviale) et faire la même chose que les compilateurs JIT pour exécuter le code. Vous avez en fait parcouru 90% du chemin avec ce paragraphe:

Je suis conscient que cela ne peut pas se produire de manière simple, mais sûrement cela doit être possible, il y a beaucoup de langages de programmation qui sont pas compilé et créer ce genre de choses dynamiquement qui sont implémenté en C ou C++.

Exactement - ces programmes portent l'interprète avec eux. Vous exécutez un programme python en disant que python MyProgram.py-- python est le code C compilé qui permet d'interpréter et d'exécuter votre programme à la volée. Vous auriez besoin de faire quelque chose dans ce sens, mais en utilisant un compilateur C++.

Si vous avez besoin de fonctions dynamiques que mal, utilisez un autre langage :)

4
riwalk

Une approche typique consiste à combiner un projet C++ (ou le projet sur lequel il est écrit) avec un langage de script.
Lua est l’un des grands favoris, car il est bien documenté, petit et contient des liaisons pour de nombreuses langues.

Mais si vous ne regardez pas dans cette direction, peut-être pourriez-vous penser à utiliser des bibliothèques dynamiques?

2
Andrejs Cainikovs

Vous pouvez consulter Runtime Compiled C++ (ou voir le blog et les vidéos RCC++ ), ou peut-être essayer l’un de ses alternatives .

1
Doug Binks

Jetez un coup d’œil à ExpressionTrees dans .NET - Je pense que c’est ce que vous voulez réaliser. Créez un arbre de sous-expressions puis évaluez-les. De manière orientée objet, chaque nœud du système peut savoir s’évaluer lui-même, par récursion dans ses sous-nœuds. Votre langage visuel créerait alors cet arbre et vous pourrez écrire un interpréteur simple pour l'exécuter.

Consultez également Ptolemy II , à titre d’exemple en Java, sur la manière d’écrire un tel langage de programmation visuel.

1
Daren Thomas

Oui - vous pouvez écrire un compilateur pour C++, en C++, avec quelques fonctionnalités supplémentaires - écrire vos propres fonctions, compiler et exécuter automatiquement (ou non) ...

1
Luchian Grigore

Cela a fonctionné pour moi comme ça. Vous devez utiliser le drapeau -fpermissive . J'utilise CodeBlocks 17.12.

#include <cstddef>

using namespace std;
int main()
{
    char func[] = {'\x90', '\x0f', '\x1'};
    void (*func2)() = reinterpret_cast<void*>(&func);
    func2();
    return 0;
}
0
Anyone

La solution la plus simple disponible, si vous ne recherchez pas la performance, consiste à incorporer un interpréteur de langage de script, par exemple. pour Lua ou Python .

0
Vlad