⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 library.txt

📁 UC Library Extensions UnderC comes with a pocket implementation of the standard C++ libraries, wh
💻 TXT
📖 第 1 页 / 共 4 页
字号:
$508    $509    $510    $511    BUILTINS
CLASSES CONSTS  CTORS   DIRECT  DO_PARENT
DTORS   FIELDS  FUNCTIONS       IMPORTS MAX_LINE
NAMESPACES      NONE    NON_STATIC      OREL    OREL_F
SREL    TEMPS   TYPEDEFS        UNDEFINED       USES_WCON
VIRTUALS        _xo_    buff    fadd    false
ff      file    fmul    fns     line
pglob   s       sl      tl      true

;> dump_entries(uc_std()->variables());
cerr    cin     cout    endl    ends
 
XNTable::variables() returns a reference to a static list, so it's not suitable for all uses.  XNTable::get_variables() is more flexible, because you may specify a wildcard pattern:

;> XEntries xl;
;> pglob->get_variables(xl,FIELDS,"_*");
;> xl.size();
(int) 1
;> xl.front()->name();
(char*) "_xo_"
;>

These wildcards are a bit braindead - either of the form "*text" or "text*", so full regular expressions are not supported (although this would not be a difficult addition if one could assume that the RX library was always available)

Similarly, XNTable::functions() and XNTable::get_functions() lists all functions matching a particular pattern:

;> XFunctions fl;
;> pglob->get_functions(fl,0,"uc_*");
;> dump_functions(fl);
void uc_cmd(const char* );
int uc_eval_method(void* sc,void* obj,void* arguments,void* result);
int uc_exec(const char* );
int uc_exec(const char* ,void* ,const char* ,int );
XNTable* uc_global();
void uc_macro_subst(const char* ,char* ,int );
void uc_result_pos(int ,char* ,int ,char* ,void* );
XNTable* uc_std();
void uc_ucri_init();
;>

Looking at the member variables of classes is an important case. The variables() function also takes a field mask, which is here used to only show non-static member variables of string.

;> XEntry* xe;
;; FOR_EACH(xe,pc->variables(FIELDS | NON_STATIC))
;1} cout << xe->name() << endl;
m_str
m_len

This is useful in generating C++ source.  For instance, C++ doesn't automatically generate "memberwise" equality operators, in the same way that it will generate memberwise assignment.  Here is a function which creates operator== for some type:

bool generate_equal_operator(const char* classname)
{
 XClass* pc = uc_global()->lookup_class(classname);
 if (! pc) return false;
 XEntry* xe;
 XEntries& xl = pc->variables(FIELDS | NON_STATIC);
// keep the name of the last member variable in this list
 string lastname = xl.back()->name();
 cout << "bool operator==(const " << classname
                      << "& a, const " << classname << "& b)"
                      << "\n{\n  return" << endl;
// Construct memberwise equality: true if all non-static members are equal
 FOR_EACH(xe,xl) {
   string name = xe->name();
   cout << "  a." << name << " == b." << name;
   cout << (name==lastname ? ";" : " &&") << endl;
 }
 cout << '}' << endl;
 return true;
}

Now, given a simple struct T2:

struct T2 {
  int a;
  char b;
  double c;
};

Then:

;> generate_equal_operator("T2");
bool operator==(const T2& a, const T2& b)
{
return
  a.a == b.a &&
  a.b == b.b &&
  a.c == b.c;
}

Obviously this isn't appropriate for more complex types (like a string class) but it's an example of the power of using C++ metadata to generate otherwise tedious code. (Another example which would be particularly useful would be a function to automatically overload operator<< to output a class to some ostream.)


Here are two useful functions (also ucri/utils.h) which get all functions and classes within a given context.  In get_matching_classes() we use a field mask to extract all entries which refer to classes or namespaces - this is more efficient than grabbing all entries and checking them individually.

int get_matching_functions(string fun_pat, XFunctions& fl, XNTable* context = NULL)
{
 context = context_from_pattern(fun_pat,context);
 context->get_functions(fl,0,fun_pat.c_str());
 return fl.size();
}

int get_matching_classes(const string& clss_pat, XClasses& cl, XNTable* context = NULL)
{
  XEntries xl;
  if (context==NULL) context=uc_global();
  context->get_variables(xl,CLASSES | NAMESPACES, clss_pat.c_str());
  XEntry* xe;
  cl.clear();
  FOR_EACH(xe,xl)
    cl.push_back(xe->type()->as_class());
  return cl.size();
}

To get _all_ functions and methods available in a system, we apply get_matching_functions() to all available classes and namespaces:

int grab_all_functions(XFunctions& fl, XClass* exclude_context=NULL)
{
 XFunctions cl_fl;
 XClass* context;
 XClasses cl;
 get_matching_functions("*",fl);
 get_matching_classes("*",cl);
 FOR_EACH(context,cl) 
  if (context != exclude_context) {
    get_matching_functions("*",cl_fl,context);
    fl.splice(fl.begin(),cl_fl);  // append the methods to the function list
  }
 return fl.size();
}

The UnderC Custom Tracing Facility (ucri/trace.h)

It is occaisionally very useful to trace function execution. Prior to vs 1.2.3, tracing was either on or it was off, and I realized that it was more manageable if tracing could be customizable.  As it turns out, a customizable tracing facility has some very interesting applications.  The basic idea is that you can attach a trace object to a function, which executes on function entry and exit.  Here is the default trace object, which allows you to control whether it's called on entry and exit;  the default operation just dumps out the function name.


class EXPORT XTrace {
private:
    bool m_on_entry, m_on_exit;
public:
    XTrace(bool on_exit = false);
    void set_on_entry(bool b) { m_on_entry = b;  }
    void set_on_exit(bool b)  { m_on_exit = b;  }
    bool do_leave()           { return m_on_exit; }
    bool do_enter()           { return m_on_entry; }

    // the XTrace interface
    virtual void enter(XExecState* xs);
    virtual void leave(XExecState* xs);
};

UCRI provides XFunction::set_trace() to attach a trace object to a particular function. You can also switch off tracing globally using XFunction::set_tracing().

;> void fred() { puts("hello"); }
;; let ffred = lookup_fun("fred");
;> let tr = new XTrace();
;> ffred->set_trace(tr);
;> for(int i = 0; i < 5; i++) fred();
*ENTER void fred()
hello
*ENTER void fred()
hello
*ENTER void fred()
hello
*ENTER void fred()
hello
*ENTER void fred()
hello
;> XFunction::set_tracing(false);
;> for(int i = 0; i < 5; i++) fred();
hello
hello
hello
hello
hello

Now this very general mechanism is too tedious for everyday use.  My general philosophy has to been to provide the basic hooks in UnderC to support a feature, and then write utilities to provide a convenient way to do common things.  Here is one way, defined in ucri/trace.h, which uses the general apply_op_to_funs() to apply a trace object to a whole set of functions:

void set_fun_trace(XFunction* f, void* ptr)
{
  f->set_trace((XTrace*)ptr);
}

void add_trace_to_funs(const string& fun_pat, XTrace* pt)
{
 XFunction::set_tracing(false);
 apply_op_to_funs(fun_pat, set_fun_trace, pt);
 XFunction::set_tracing(true);
}

Here it is used to attach our trace object to all methods of string. You immediately start seeing the destruction of all the temporaries generated in string operations. 

;> add_trace_to_funs("string::*",pt);
*ENTER string::~string()
;> string s = "hello dolly";
*ENTER string::string(const char* str)
;> s = s + " you're so fine";
*ENTER string& string::operator=(const string& s)
(string&) 'hello dolly you're so fine'
*ENTER string::~string()
*ENTER string::~string()
;>

By the way, it's an interesting exercise to use the old string header (#include <old-string>) and repeat this exercise. Then you will see the inner workings of the UC string class, which also illustrates my point that tracing to be useful needs to be controlled more finely.

;> s = s + " you're so fine";
*ENTER string::string(const string& s)
*ENTER void string::resize(unsigned long int sz)
*ENTER void string::append(char* s)
*ENTER void string::resize(unsigned long int sz)
*ENTER string::string(const string& s)
*ENTER void string::resize(unsigned long int sz)
*ENTER string::~string()
*ENTER string& string::operator=(const string& s)
*ENTER void string::resize(unsigned long int sz)
(string&) 'hello dolly you're so fine'
*ENTER string::~string()

To remove the trace from functions set with add_trace_funs(), use remove_trace() which calls f->set_trace(NULL) for all functions in the set.

void remove_trace(const string& fun_pat)
{
 add_trace_to_funs(fun_pat,NULL);
}

The general power of the XTrace mechanism comes when you derive your own custom trace class.  Since UnderC allows you to override imported methods, you are effectively injecting your own code into the UnderC runtime:

 int kount;
 
 class MyTrace: public XTrace {
 public:
    virtual void enter(XExecState* xs)
    {
       kount++;
    }
 };

This simple trace object, which counts function calls, is here applied to string::~string - please note that you need to use its internal name __D__.  Often we simply need to know some simple function call statistics;  later I'll show you a profiler application which takes this idea to its logical conclusion.  (Or, if you have an external interface like a set of flashing lights, you can make them blink in time to your function calls.)

;> kount = 0;
;> let f = lookup_fun("string::__D__");
;> let tr = new MyTrace();
;> f->set_trace(tr);
;> string s = "hello";
;> s = s + " dog";
(string&) 'hello dog'
;> kount;
(int) kount = 1

Note that XTrace::enter() is passed a pointer to an XExecState.  This is useful information about the machine state at this point, like the current function being called, the stack frame, the previous function, etc.  Here's its definition in xtrace.h (found in source dir)

struct XExecState {
    FBlock* fb;        // currently executing function block
    int*    sp;        // stack pointer
    char*   op;        // object pointer
    void*   ip;        // instruction pointer
    int*    bp;        // base pointer (args and locals are relative to this) 
    FBlock* last_fb;   // from where we've been 
    void*   last_ip;   // called...
    int*    last_bp;   // base pointer of calling context...
};

Since this isn't a tutorial on UnderC internals, I won't bore you with all the details. Here is what you need to know;  fb is the address of the function, whereas ip is the actual code address at which you will start executing. If the function is a method, then op will be the address of the object (the 'this' pointer). The base pointer bp allows you direct access to the arguments of the function, and last_fb and last_ip tell you where this function has been called from, which allows you to restrict tracing to particular uses of a function. 

XFunction::pcode() will tell you ip anyway, but what's particularly useful about ip is that you can _modify_ these values. In particular, you can modify fb,ip,sp and bp.  (see engine.cpp, line 718).  This allows a few entertaining hacks, like redirecting a function call to some other routine, etc.  

This example shows a trace object which will show you the caller function:

 string fun_as_str(XFunction* f)
 {
    string s;
    f->as_str(s);
    return s;
 }

 class MyTrace: public XTrace {
 public:
    virtual void enter(XExecState* xs)
    {
       XFunction* called_from = XFunction::from_fb(xs->last_fb);
       cout << "called from " << fun_as_str(called_from) << endl;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -