Home > Software engineering >  debugging with c templates
debugging with c templates

Time:11-24

The problem: I needed to find when & where a structure member was modified.

I work on a large, real-time system, parts of which are old were originally written in C. In this case, structs were never classes and did not have private members, so everything is flapping in the breeze.

I have very good analysis and debugging tools, but the setting of one structure member evaded static analysis, and gdb watches were difficult because there we so many instances of the structure. Also running gdb on a real-time system can have strange results.

Here's a template I used to change the member declaration from int to a template instance that would report an assignment. This worked in all but two cases.

  • I could not compose an overload that would allow the template's value to be cast to an enum (I could cast it to a float). This was solved by adding a *, thus forcing the * operator. But it would have been best if I could declare an overload that would avoid that. Fortunately there was only a single instance in the code. See the error below.
  • Initially I had what I thought was a reference operator T& operator&(), but finally found while writing this that operator T& () was the correct solution. Why didn't the first one work?
    #include <stdio.h>
    
    template <typename T> class TraceType {
    private:
        T value;
    public:
        TraceType()             { value = (T)~0; }
        T operator=(T v)        { notify(v); value = v; return v; }
        T operator->() const    { return value; }
        T operator*() const     { return value; }
        operator T& ()          { return value; }
        operator T () const     { return value; }
        bool operator==(const T rhs) const
                                { return value == rhs; }
        void notify(T);
    };
    
    struct Foo {
        int x;
        // this should have been the only change necessary
        // need to find when/where this gets changed
        // int y;
        TraceType<int> y;
        int z;
    };
    
    const char* getStackTrace() { return "<stack trace"; }
    
    template<> void TraceType<int>::notify(int v)
        { printf("y<--%d %s\n", v, getStackTrace()); }
    
    enum Special { one, two, three };
    
    int main(int argc, char** argv)
    {
        Foo foo;
        foo.x = 1;
        foo.y = 2;
        foo.z = 3;
    
        Foo* fooPtr = &foo;
        // error: invalid cast from type ‘TraceType<int>’ to type ‘Special’
        // Special xxx = (Special)fooPtr->y;
    
        Special xxx = (Special)*fooPtr->y; // this works
        float yyy = (float)fooPtr->y;      // this works
    
        int& refToY = foo.y;               // this works
    }

CodePudding user response:

Add a templated conversion operator:

template <typename T> class TraceType {
private:
    T value;
public:
    TraceType()             { value = (T)~0; }
    T operator=(T v)        { notify(v); value = v; return v; }
    T operator->() const    { return value; }
    T operator*() const     { return value; }
    operator T& ()          { return value; }
    operator T () const     { return value; }
    bool operator==(const T rhs) const
                            { return value == rhs; }
    // Templated conversion operator
    template <class U> operator U() const { return static_cast<U>(value); }
    void notify(T);
};

CodePudding user response:

Regarding your attempt to implement: T& operator&() ...
operator&() (without parameters) is the address-of operator.


Generally, you should not overload the address-of operator.


However, if you do, then let it return the address of the represented object.

T * operator & () { return &value; }
  • Related